diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:54:46 +0000 |
commit | cd7b005519ade8ab6c97fcb21590b71b7d1be6e3 (patch) | |
tree | c611a8d0cd5e8f68f41b8c2d16ba580e0f40a38d | |
parent | Initial commit. (diff) | |
download | librtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.tar.xz librtr-cd7b005519ade8ab6c97fcb21590b71b7d1be6e3.zip |
Adding upstream version 0.8.0.upstream/0.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
153 files changed, 31874 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..95d3b73 --- /dev/null +++ b/.clang-format @@ -0,0 +1,47 @@ +--- +AlignAfterOpenBracket: true +AlignEscapedNewlines: Left +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: false +BasedOnStyle: LLVM +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Linux +BreakBeforeTernaryOperators: false +BreakStringLiterals: false +ColumnLimit: 120 +CommentPragmas: ^ cppcheck-supress +ContinuationIndentWidth: 8 +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '"rtrlib/.+"' + Priority: 1 + - Regex: '"third-party/.+"' + Priority: 2 + - Regex: '.+_private.+' + Priority: 0 + - Regex: '"[[:alnum:]_]+"' + Priority: 0 + - Regex: '".+"' + Priority: 0 + - Regex: '<.+>' + Priority: 3 +IncludeIsMainRegex: '(private)?$' +IndentCaseLabels: false +IndentWidth: 8 +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +MaxEmptyLinesToKeep: 1 +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +ReflowComments: false +SortIncludes: true +SpaceAfterCStyleCast: false +UseTab: Always diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..87f3b1e --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,17 @@ +codecov: + token: 8ee1ecb1-4ff3-4aa4-b9c1-61c70a0afbe6 + branch: master + +coverage: + precision: 2 + round: down + range: "50...80" + + ignore: + - third-party/ + - tests/* + + status: {} + +comment: + layout: header, changes, diff diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..391a6cd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org +root = true + +[*.{c,h}] +indent_style = tab +tab_width = 8 + +[*.sh] +indent_style = space +indent_size = 4 + +[{.travis.yml,CMakeLists.txt}] +indent_style = space +indent_size = 4 + +[*.md] +indent_style = space +indent_size = 2 +max_line_width = 72 diff --git a/.git-blame-ignore b/.git-blame-ignore new file mode 100644 index 0000000..cb014f6 --- /dev/null +++ b/.git-blame-ignore @@ -0,0 +1,3 @@ +642ad37219aead62bc138c4a40b5f56be7e1c487 +8c6f66c0fabc97a011c3a32b54e27535145b52ec +b946a2c133199bf23cfdc838ef74d272483abfb1 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b3d4372 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +### Description +<!-- +Example: Uninitialized use of stack variables in rtr_sync_receive_and_store_pdus. +--> + +### Steps to reproduce the issue +<!-- +Try to describe as precisely as possible the steps required to reproduce +the issue. Here, you can also describe your RTRlib configuration (e.g., to +which cache server you connect). +--> + +### Expected results +<!-- +Example: The variables `pfx_shadow_table` and `spki_shadow_table` in +`packets.c:rtr_sync_receive_and_store_pdus` should be initialized at least +with `NULL`. +--> + +### Actual results +<!-- +Please paste or specifically describe the actual output and implications. +--> + +### Versions +<!-- +Operating system: Mac OSX, Linux +Build environment: GCC +RPKEI Cache server (optional): Routinator, RIPE Validator, rpki.net etc. +--> + +<!-- Thanks for contributing! --> + diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..6b7c217 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,17 @@ +--- +name: Enhancement Request +about: Ask for the enhancement of an existing feature. + +--- + +### Description of current state +<!-- Please describe the current state and why it is important to enhance. --> + +### Improvement +<!-- Please describe the enhancement. --> + +### Useful links +<!-- Please include links to any documentation that you think is useful. --> + +<!-- Thanks for contributing! --> + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..398efac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,15 @@ +--- +name: Feature Request +about: Ask for missing features. + +--- + +### Description +<!-- Please describe your use case, why you need this feature, and why this +feature is important for RTRlib. --> + +### Useful links +<!-- Please include links to any documentation that you think is useful. --> + +<!-- Thanks for contributing! --> + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e5d42cc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ +<!-- +The RTRlib community cares about code quality. Therefore, before +describing what your contribution is about, we would like you to make sure +that your modifications are compliant with the RTRlib coding conventions, see +https://github.com/rtrlib/rtrlib/blob/master/CONTRIBUTING. +--> + +### Contribution description + +<!-- +Put here the description of your contribution: +- describe which part(s) of RTRlib is (are) involved +- if this is a bug fix, describe the bug that it solves and how it is solved +- you can also give more information to reviewers about how to test your changes +--> + + +### Testing procedure + +<!-- +Details steps to test your contribution: +- which test/example to compile and is there a 'test' command +- how to know that it was not working/available in master +- the expected success of the test output +--> + + +### Issues/PRs references + +<!-- +Examples: Fixes #212. See also #196. Depends on PR #188. + +Please use keywords (e.g., fixes, resolve) with the links to the issues you +resolved. This way they will be automatically closed when your pull request +is merged. See https://help.github.com/articles/closing-issues-using-keywords/. +--> + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a1216c1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +dist: xenial + +language: c + +env: + global: + # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created + # via the "travis encrypt" command using the project repo's public key + - secure: "esfY0Q8S2UMLS3V0B/dSqIFQYChACXwJRBmtHTh87dvYMIS9NC2Bv/CVA7UAVIPSP2S1Az2TW1uNtXg3Xa5FjRY/25Zzls3jdeC83HtH6JPd+Hu55V7kq9j3P5ekPr6N7J+bwQosWISGgOsMkh1sbfgtLqsWRCabdLyIzBQpQfuvpP/7MZkL/QeghDOP1WW9I9MFib6w2drcxyI+QItPZ/xTcZc/XIeuDCLynqH9Fzyvy0FnhR1M5xJf7VCDa1whR7l3N2wLZvYtoByMp9wIS8RctW6dPWquQ7vpqzLdMm9fz7jpbrX0WOTUApXu6pPtP1IJAcBvyzmq9NE7ypxha/reJM8ESINg5Gb8MvTHwUKmf1aTLp8DqqjF6hhMRMjAHG8cCnbry+AdA5gpfSz5BLsfe4OvIpV3XiladRTY9vBuv5XMgJWq+r/ONaTURrd8bNZqW+qcck+yfPMVyzQKqo/xkzPBApcSseoain/loE4vhlRo73SqByrTYyt+qiJd22zBBFEpZGSHtCpF7XKrxj9tkyNteSUReXqxH4hQfnTQMKDbtV/A1Ki9p40NIpj+EGeSsTULZBv8FKo4OJ5odq+EkEig/GEgB55ZB+1j7A7oXo5u/UDKDegIJ5DfbJ+fu0xN543cmV4X0USBQ2jgYiHhFhpmRwhv75O3iNsbEWM=" + +addons: + coverity_scan: + project: + name: "rtrlib/rtrlib" + description: "Build submitted via Travis CI" + notification_email: rtrlib@googlegroups.com + build_command_prepend: "cmake -D CMAKE_BUILD_TYPE=Release -DENABLE_COVERAGE=On -DUNIT_TESTING=On ." + build_command: "make" + branch_pattern: coverity_scan + + apt: + packages: + - cppcheck + - doxygen + - libssh-dev + - libcmocka-dev + - ctags + +script: + - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then scripts/travis.sh; fi + +after_success: + - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..edf73da --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,177 @@ +For details, please check the commit history: +https://github.com/rtrlib/rtrlib/commits/master + +Release History: + +* Version 0.8.0 + - Fix blocking when stopping sockets in some cases + - Add callback to allow user created sockets + - Add ability to change the connect timeout + - Add support for password authentication to ssh transport + - Update tools to allow for password authentication when using ssh transport + - Improve error handling in ssh transport + - Improve error output in TCP transport + - Improve libssh detection when running configure + - Initialise structures properly in rtrclient + - Fix several compiler warnings + - Extend log messages to include prefixes when using ssh transport + - Fix bug that constantly called tr_recv because of ignoring timeout argument when using ssh transport + - Apply kernel coding style in all source files + +* Version 0.7.0 + - Fix to ensure shadow tables are only freed when initialized before + - Fix SSH transport w/o bind address and add host key verification + - Fix bug in cache groups to ensure uniqueness of cache server preferences + - Fix race condition in rtr_stop + - Remove error PDU null termination check + - Update tommyds implementation to version 2.2 + - tools/rpki-rov: improve validation of command line arguments + - tools/rtrclient: add ROA export with templates, support for CSV and JSON + - Output log messages to stderr instead of stdout + - style: increase max line length to 120 characters + +* Version 0.6.3 + - Fix IPv4 string conversion to support big endian systems + +* Version 0.6.2 + - Add C++ support by disabling name mangling for RTRlib symbols + - Make pfx_table, pfx_table_init(), pfx_table_add(), + pfx_table_free(), pfx_table_src_remove() public; to use RTRlib + without online connection to an RPKI cache server + - Hide symbols of Tommy hash table without using forward declaration, + to resolve dependency on C11 + - Fix cmake build errors in Debian and OpenBSD + - Add connection timeout in tests/test_dynamic_groups + +* Version 0.6.1 + - Fix for cmake versions >= 2.8 + - Fix rpm build + - Fix Ubuntu 12.04 build + +* Version 0.6.0 + - Change default symbol visibility: Hide all symbols per default, + export public symbols explicitly + - Add API to configure interval mode: {set,get}_interval_mod + - Change naming of interval enumerations in rtr.h: add prefixes to + prevent name collisions + - Fix Debian packages: merge rtrclient and rtr-validator into one + package, use libjs-jquery, fix copyright format, fix version + numbering and source package format + - Fix bug in trie structure where deletion of inner nodes may lead + to incorrect reordering, i.e., a node with longer prefix length + overrules nodes with shorter prefix length, which then will not + be found anymore + - Rename cli-validator to rpki-rov + - Fix wrong length of error text in error pdu + - Add atomtic reset for pfx table and spki table + - Improve code for packets + - Add rpm package build infrastructure + - Add cppcheck suppressions for public api functions + - Add new test to verify correctness of prefix removal + - Remove function rtr_mgr_find_group in RTR Manager because it is + not used + - Disable unittests for Apple macOS + - Add man pages for tools + +* Version 0.5.0 + - Add support for dynamic add and remove of socket groups in rtr_mgr + - add memory handling wrappers (i.e. alloc utils) for FRR integration + - extend tests to verify dynamic groups feature + - fix duplicate socket configuration on init in {tcp,ssh}_transport + - Improve documentation on trie implementation and for util functions + +* Version 0.4.0 + - Fix bug in prefix origin AS validation caused by LPFST, which lead to + incrrect validation results in corner caes, and change prefix storage + structure to Trie + - Fix memory leaks in lpfst_get_children + - Fix memory leak in rtr_mgr_get_spki + - Fix memory leak in rtr_mgr_init + - Fix memory leak in pfx_table_append_elem + - Fix memory leak in pfx_table_del_elem + - Fix byte order for encapsulated error PDUs + - Fix error PDU handling, do not send encapsulated PDUs on internal errors + - Improve testing: + - add extensive unit tests based on the Cmocka framework + - add runtime tests, e.g. live validation of RIPE RIS BGP beacons + - Improve overall coding style and documentation + - Add static code analysis (cppcheck, codecov.io) to Travis CI config + - Add backward compatibility for libssh 0.5.0 and Ubuntu 12.04 LTS + - Update Debian package build files + +* Version 0.3.6 + - rtrlib/lib/*: Change API and add lrtr_ prefix to prevent namespace + conflicts with third party applications + - Change API for rtr_mgr_init and rtr_init to improve error handling + - Change preference order of finding cache servers, according to + RFC 6810 + - Improve behavior of cache server groups, switch to preferred + group as soon as cache server is online again + - Improve error handling in rtr_mgr_init + - Improve timing behavior (expire interval, refresh interval etc.) + - Fix bug in rtr_mgr_close_all_groups_except_one to prevent deadlock + - Fix memory leaks, race condition, and buffer overflow/underflow + - Improve debugging and status values + - Update SSH transport to new LibSSH API + +* Version 0.3.5 + - Change license from LGPLv3 to MIT license + - Fix bug in pfx_table_src_remove, a lock was not released + - Fix bug in rtr_purge_outdated_records that removed prefixes + incorrectly + - Create and install rtrlib.pc pkg-config + - Update code style + - Add support for OS X (10.10) + - Add new tool cli-validator + +* Version 0.3.0 + - Added support for IETF draft draft-ietf-sidr-rpki-rtr-rfc6810-bis-02 + - Minor changes of the library API (see doxygen documentation). + +* Version 0.2.4 + - rtrclient: fix rtrclient didn't compile when libssh wasn't + installed and found by cmake + +* Version 0.2.3 + - rtrclient: increase polling period from 1 to 30 seconds + - rtrclient: bug fix for segfault caused by out-of-scope variable access + - rtrclient: fix wrong ssh example in usage output + - transport: remove const modifier from tr_ssh_config and + tr_tcp_config struct members + - cmake: install doxygen documentation to $PREFIX/share/doc/rtrlib/ + - add debian packaging files + +* Version 0.2.2 + - Fixed a bug in ipv6_get_bits(..), specific IPv6 records couldn't + be addded to the pfx_table + +* Version 0.2.1 + - Nonce variable renamed to session_id to conform with + draft-ietf-sidr-rpki-rtr-26 + - Warning message added if the Zero field of a prefix PDU doesn't + contain 0 + - pfx_validate_r function added, returns list of prefixes which + affected the validation state BGP route + - Fixed bug in lpfst_remove that could cause that an pfx_record in the + pfx_table could not be found. + - Added state rollback to the prefix synchronization function to + assure that the last correct state is recovered if an error occurs + during synchronization + - Few smaller bugfixes and debug formatting corrections + +* Version 0.2 + - Support of RTR-Server failover mechanisms (RTR manager component + implemented) + - Automatic reconnect of rtr_socket in case of errors + - Renamed rtr_update_fp callback to pfx_update_fp. Callback will be + executed only if a pfx_record has been added or deleted. + - Representation of IP addresses in host byte order within pfx_table + - New convenience function to transform an IP address string into a + struct ip_addr and vice versa + - support for the IETF drafts draft-ietf-sidr-rpki-rtr-19 and + draft-ietf-sidr-pfx-validate-03 + - Extended debug messages + - Many bug fixes + +* Version 0.1 + - Beta version diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7e9aa98 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,177 @@ +project(rtrlib C) + +set(PROJECT_DESCRIPTION "Lightweight C library that implements the RPKI/RTR protocol and prefix origin validation.") + +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=gnu99 -fstack-protector-all") +if(CMAKE_BUILD_TYPE STREQUAL Debug) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -Wformat-security -Winit-self -Wundef -Wwrite-strings") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat=2 -Werror=missing-prototypes -Werror=missing-declarations") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-format-attribute -Werror=implicit-function-declaration") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=bad-function-cast -Werror=return-type") + + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) + if(GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wfree-nonheap-object") + endif() + + if(GCC_VERSION VERSION_GREATER 5.1 OR GCC_VERSION VERSION_EQUAL 5.1) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=incompatible-pointer-types") + endif() + + if(GCC_VERSION VERSION_GREATER 8.0 OR GCC_VERSION VERSION_EQUAL 8.0) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=cast-function-type") + endif() + + set(DOCS_EXCLUDE_PATTERN "") +else() + set(DOCS_EXCLUDE_PATTERN "*_private.h") +endif(CMAKE_BUILD_TYPE STREQUAL Debug) + +include_directories(.) + +add_subdirectory(third-party) + +find_package(codecov) + +find_package(Threads REQUIRED) +if(NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + find_library(RT_LIB rt) +endif(NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + +include(GNUInstallDirs) # for man page install path + +set(RTRLIB_SRC rtrlib/rtr_mgr.c rtrlib/lib/utils.c rtrlib/lib/alloc_utils.c rtrlib/lib/convert_byte_order.c + rtrlib/lib/ip.c rtrlib/lib/ipv4.c rtrlib/lib/ipv6.c rtrlib/lib/log.c + rtrlib/pfx/trie/trie.c rtrlib/pfx/trie/trie-pfx.c rtrlib/transport/transport.c + rtrlib/transport/tcp/tcp_transport.c rtrlib/rtr/rtr.c rtrlib/rtr/packets.c + rtrlib/spki/hashtable/ht-spkitable.c ${tommyds}) +set(RTRLIB_LINK ${RT_LIB} ${CMAKE_THREAD_LIBS_INIT}) + +include(FindPkgConfig) + +if (NOT DEFINED RTRLIB_TRANSPORT_SSH OR RTRLIB_TRANSPORT_SSH) + pkg_check_modules(LIBSSH libssh>=0.5.0) + + if(LIBSSH_FOUND) + # extract version number components + string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" RTRLIB_LIBSSH_VERSION_MATCH ${LIBSSH_VERSION}) + if (NOT RTRLIB_LIBSSH_VERSION_MATCH) + message(FATAL_ERROR "Could not parse libssh version ${LIBSSH_VERSION}") + endif(NOT RTRLIB_LIBSSH_VERSION_MATCH) + SET(LIBSSH_VERSION_MAJOR ${CMAKE_MATCH_1}) + SET(LIBSSH_VERSION_MINOR ${CMAKE_MATCH_2}) + SET(LIBSSH_VERSION_PATCH ${CMAKE_MATCH_3}) + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D LIBSSH_VERSION_MAJOR=${LIBSSH_VERSION_MAJOR} -D LIBSSH_VERSION_MINOR=${LIBSSH_VERSION_MINOR}") + set(RTRLIB_HAVE_LIBSSH 1) + include_directories(${LIBSSH_INCLUDE_DIRS}) + set(RTRLIB_SRC ${RTRLIB_SRC} rtrlib/transport/ssh/ssh_transport.c) + set(RTRLIB_LINK ${RTRLIB_LINK} ${LIBSSH_LIBRARIES}) + message(STATUS "libssh found, building librtr with SSH ${LibSSH_VERSION} support") + elseif(NOT LIBSSH_FOUND AND RTRLIB_TRANSPORT_SSH) + message(FATAL_ERROR "libssh >= 0.5.0 not found but ssh support was requested. Omit RTRLIB_TRANSPORT_SSH to build without ssh support.") + else() + message(WARNING "libssh >= 0.5.0 not found") + endif(LIBSSH_FOUND) +endif(NOT DEFINED RTRLIB_TRANSPORT_SSH OR RTRLIB_TRANSPORT_SSH) + +#doxygen target +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doxygen/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile @ONLY) + + add_custom_target(doc ALL ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating doxygen API documentation" VERBATIM) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs DESTINATION share/doc/rtrlib) +endif(DOXYGEN_FOUND) + +add_library(rtrlib SHARED ${RTRLIB_SRC}) +add_coverage(rtrlib) +target_link_libraries(rtrlib ${RTRLIB_LINK}) +set_target_properties(rtrlib PROPERTIES C_VISIBILITY_PRESET hidden) + +add_library(rtrlib_static STATIC ${RTRLIB_SRC}) +add_coverage(rtrlib_static) +target_link_libraries(rtrlib_static ${RTRLIB_LINK}) + +ADD_SUBDIRECTORY(tools) + +ADD_SUBDIRECTORY(doxygen/examples) + +include(AddTest) +ADD_SUBDIRECTORY(tests) +ENABLE_TESTING() +ADD_TEST(test_pfx tests/test_pfx) +ADD_TEST(test_trie tests/test_trie) +#ADD_TEST(test_pfx_locks tests/test_pfx_locks) + +ADD_TEST(test_ht_spkitable tests/test_ht_spkitable) +ADD_TEST(test_ht_spkitable_locks tests/test_ht_spkitable_locks) + +ADD_TEST(test_live_validation tests/test_live_validation) + +ADD_TEST(test_ipaddr tests/test_ipaddr) + +ADD_TEST(test_getbits tests/test_getbits) + +ADD_TEST(test_dynamic_groups tests/test_dynamic_groups) + +#install lib +set (RTRLIB_VERSION_MAJOR 0) +set (RTRLIB_VERSION_MINOR 8) +set (RTRLIB_VERSION_PATCH 0) +CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/rtrlib/rtrlib.h.cmake ${CMAKE_SOURCE_DIR}/rtrlib/rtrlib.h) +set(LIBRARY_VERSION ${RTRLIB_VERSION_MAJOR}.${RTRLIB_VERSION_MINOR}.${RTRLIB_VERSION_PATCH}) +set(LIBRARY_SOVERSION ${RTRLIB_VERSION_MAJOR}) +set_target_properties(rtrlib PROPERTIES SOVERSION ${LIBRARY_SOVERSION} VERSION ${LIBRARY_VERSION} OUTPUT_NAME rtr) +install(TARGETS rtrlib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/) + + +# Get lists of all headers +file(GLOB_RECURSE RTRLIB_HEADERS RELATIVE "${PROJECT_SOURCE_DIR}/rtrlib" "rtrlib/*.h") +# Create list of all public headers +foreach(ITEM ${RTRLIB_HEADERS}) + if(NOT ${ITEM} MATCHES ".*_private\\.h" AND NOT ${ITEM} MATCHES ".*tommy.*") + list(APPEND RTRLIB_PUBLIC_HEADERS ${ITEM}) + endif() +endforeach() + +#install includes +foreach(ITEM ${RTRLIB_PUBLIC_HEADERS}) + get_filename_component(HEADER_DIRECTORY "${ITEM}" PATH) + install(FILES "rtrlib/${ITEM}" + DESTINATION "include/rtrlib/${HEADER_DIRECTORY}") +endforeach() + +#pkgconfig file +if(RTRLIB_TRANSPORT_SSH) + set (PKG_CONFIG_REQUIRES "libssh >= 0.5.0") +endif(RTRLIB_TRANSPORT_SSH) + +# '#include <rtrlib/rtrlib.h>' includes the "rtrlib/" +set (PKG_CONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") +set (PKG_CONFIG_INCLUDEDIR "\${prefix}/include") +set (PKG_CONFIG_LIBS "-L\${libdir} -lrtr") +set (PKG_CONFIG_CFLAGS "-I\${includedir}") + +configure_file ( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" +) +install ( + FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" +) +#add uninstall target +configure_file( + "cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + +coverage_evaluate() diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..2b1752f --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,62 @@ +We are very much looking forward to your contribution to the RTRlib. +Before you start, please consider the advice below. + + +Code Contributions +------------------ + +If you intend to submit a major code contribution (e.g., new feature, +extended modification of existing code), we highly encourage you to +discuss this contribution with the RTRlib community using the Github +Issue Tracker before you submit your pull request. + +In any case, proceed along the steps: + +1. Fork the RTRlib Git repository (if you haven't done this already) + +2. Create a branch + +3. Make commits (details see below) + +4. Make sure your code complies with Kernel Coding Style + + * You can check this by running scripts/check-coding-style.sh <your file> + +5. Submit a pull request + + * If necessary, squash unnecessary commits before submitting the PR. + + * We use a Continuous Integration system, which is linked in Github. + The results of the automatic tests are shown at the bottom of your + pull request. + +6. Other RTRlib members will provide feedback + +7. Address the feedback + + * If necessary, squash unnecessary commits. + + +Commit Messages +--------------- + +Commit messages should follow the structure + + First line: modulename: brief description (max 50 chars) + Second line: <The second line is empty> + Further lines: More detailed description + +The description should be written in imperative and not in the past +tense (e.g., "Remove unnecessary includes from header file" instead of +"Unnecessary includes from header file removed"). Please be precise in +the detailed description. For an example of a nice commit message see +https://github.com/rtrlib/rtrlib/commit/f79471e43aa3eee8772360300661f8ead21f15f2. + +General information about good commits is available here + + * http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html + + * http://git-scm.com/book/ch5-2.html + + +/* vim: set tw=72 sts=2 sw=2 ts=2 expandtab: */ @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011 - 2017 The RTRlib authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..28b4895 --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +[![Build Status][travis-badge]][travis-link] +[![GitHub release][release-badge]][release-link] +[![License][license-badge]][license-link] +[![API docs][api-badge]][api-link] +[![Wiki][wiki-badge]][wiki-link] +[![Read the Docs][rtd-badge]][rtd-link] +[![Help wanted][helpwanted-badge]][helpwanted-link] + +Introduction +------------ +The RTRlib implements the client-side of the RPKI-RTR protocol +([RFC 6810](https://tools.ietf.org/html/rfc6810)), +([RFC 8210](https://tools.ietf.org/html/rfc8210)) and BGP Prefix Origin +Validation ([RFC 6811](https://tools.ietf.org/html/rfc6811)). This also enables +the maintenance of router keys. Router keys are required to deploy BGPSEC. + +The software was successfully tested on Linux and FreeBSD. + +This package contains also the `rtrclient` program. It connects to an +RTR-Server over TCP or SSH and shows on STDOUT prefix origin data and +router keys that have been received from the RTR server. The program can +be found in the bin/ directory. + +Requirements +------------ +To build the RTRlib, the CMake build system must be installed. + +To establish an SSH connection between RTR-Client and RTR-Server, the +libssh 0.6.x or higher library must also be installed. + +cmocka (optional) is required for unit tests +Doxygen (optional) is required to create the HTML documentation. + + +Compilation +----------- + +* Generate Makefile: + + Without debugging options + + cmake -D CMAKE_BUILD_TYPE=Release . + + With debug symbols and debug messages: + + cmake -D CMAKE_BUILD_TYPE=Debug . + + If the libssh isn't installed within the systems include and library + directories you can run cmake with the following parameters: + + -D LIBSSH_LIBRARY=<path-to-libssh.so> + -D LIBSSH_INCLUDE=<include-directory> + + If libssh is installed but you do not want to build rtrlib with ssh + support, you can disable it with the following parameter: + + -D RTRLIB_TRANSPORT_SSH=No + + Or to enforce ssh support: + + -D RTRLIB_TRANSPORT_SSH=Yes + + To specify another directory where the RTRlib will be installed, you + can pass the following argument to cmake: + + -D CMAKE_INSTALL_PREFIX=<path> + +* Build library, tests, and tools + + make + + +Installation +------------ +To copy libraries and headers to system directories, run (optional): + + make install + + +Linking to RTRlib +---------------------- +The name of the shared library is rtr. To link programs to the RTRlib, +pass the following parameter to gcc: + + -lrtr + +In case an error such as + + -/usr/bin/ld: cannot find -lrtr + -collect2: error: ld returned 1 exit status + +occurs, the location of the library can be passed explicitly as a parameter + + -L<path_to_librtr.so> + +e.g., + + -L/usr/local/lib64/ + + +API Documentation +----------------- +The RTRlib includes a HTML documentation of the API. To build them, +doxygen must be installed. The documentation will be located in the +docs/ directory after the execution of: + + make doc + + +Test RTR-Server Connection +-------------------------- +The following command establishes a plain TCP connection to an +RTR-Server using the rtrclient command line tool: + + bin/rtrclient tcp rpki-validator.realmv6.org 8282 + +`rpki-validator.realmv6.org` is an open RTR-Server instance for testing +purposes, which runs the RIPE Validator. It listens on port 8282 and +validates ROAs from the following trust anchors: AfriNIC, APNIC, ARIN, +LACNIC, RIPE. + + +Directories +----------- +* cmake/ - CMake modules +* doxygen/ - Example code and graphics used in the Doxygen + documentation +* rtrlib/ - Header and source code files of the RTRlib +* tests/ - Unit tests +* tools/ - Contains the rtrclient + + +CONTRIBUTE +---------- +To contribute something to RTRlib, please refer to our [contributing document](CONTRIBUTING). + + +Contact +------- +Website: [http://rtrlib.realmv6.org/](http://rtrlib.realmv6.org/) + +Mailing List: [rtrlib@googlegroups.com](mailto:rtrlib@googlegroups.com) + + +[travis-badge]:https://travis-ci.com/rtrlib/rtrlib.svg?branch=master +[travis-link]:https://travis-ci.com/rtrlib/rtrlib +[release-badge]: https://img.shields.io/github/release/rtrlib/rtrlib.svg +[release-link]: https://github.com/rtrlib/rtrlib/releases/latest +[license-badge]: https://img.shields.io/github/license/rtrlib/rtrlib +[license-link]: https://github.com/rtrlib/rtrlib/blob/master/LICENSE +[api-badge]: https://img.shields.io/badge/docs-API-informational.svg +[api-link]: http://rtrlib.realmv6.org/doxygen/latest/ +[wiki-badge]: https://img.shields.io/badge/docs-Wiki-informational.svg +[wiki-link]: https://github.com/rtrlib/rtrlib/wiki +[rtd-badge]: https://readthedocs.org/projects/rtrlib/badge/?version=latest +[rtd-link]: http://rtrlib.readthedocs.io/en/latest/?badge=latest +[helpwanted-badge]: https://img.shields.io/badge/help-wanted-orange.svg +[helpwanted-link]: https://github.com/rtrlib/rtrlib/issues diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..d943aea --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,24 @@ +#http://www.itk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F +cmake_policy(SET CMP0007 NEW) +if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +list(REVERSE files) +foreach (file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + execute_process( + COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval + ) + if(NOT ${rm_retval} EQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif (NOT ${rm_retval} EQUAL 0) + else (EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif (EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/cmake/modules/AddTest.cmake b/cmake/modules/AddTest.cmake new file mode 100644 index 0000000..10da459 --- /dev/null +++ b/cmake/modules/AddTest.cmake @@ -0,0 +1,31 @@ +enable_testing() +include(CTest) + +function (ADD_RTR_TEST _testName _testSource) + add_executable(${_testName} ${_testSource}) + target_link_libraries(${_testName} ${ARGN}) + add_test(${_testName} ${CMAKE_CURRENT_BINARY_DIR}/${_testName}) +endfunction (ADD_RTR_TEST) + + +function (ADD_RTR_UNIT_TEST _testName _testSource) + add_rtr_test(${_testName} ${_testSource} ${ARGN}) + add_coverage(${_testName}) +endfunction (ADD_RTR_UNIT_TEST) + + +function (WRAP_FUNCTIONS _testName) + set(template " -Wl,--wrap=") + set(linkopts "") + foreach(f ${ARGN}) + string(CONCAT linkopts ${linkopts} ${template} ${f}) + endforeach() + + get_target_property(temp ${_testName} COMPILE_FLAGS) + if (temp) + string(CONCAT linkopts ${temp} ${linkopts}) + endif() + set_target_properties(${_testName} + PROPERTIES + LINK_FLAGS ${linkopts}) +endfunction (WRAP_FUNCTIONS) diff --git a/cmake/modules/FindGcov.cmake b/cmake/modules/FindGcov.cmake new file mode 100644 index 0000000..8ffb7c6 --- /dev/null +++ b/cmake/modules/FindGcov.cmake @@ -0,0 +1,171 @@ +# This file is part of CMake-codecov. +# +# CMake-codecov is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful,but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see +# +# http://www.gnu.org/licenses/ +# +# +# Copyright (c) +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Written by Alexander Haase, alexander.haase@rwth-aachen.de +# + + +# include required Modules +include(FindPackageHandleStandardArgs) + + +# Search for gcov binary. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + # Gcov evaluation is dependend on the used compiler. Check gcov support for + # each compiler that is used. If gcov binary was already found for this + # compiler, do not try to find it again. + if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN) + get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) + + if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") + # Some distributions like OSX (homebrew) ship gcov with the compiler + # version appended as gcov-x. To find this binary we'll build the + # suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+" GCC_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov + HINTS ${COMPILER_PATH}) + + elseif ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "Clang") + # Some distributions like Debian ship llvm-cov with the compiler + # version appended as llvm-cov-x.y. To find this binary we'll build + # the suggested binary name with the compiler version. + string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION + "${CMAKE_${LANG}_COMPILER_VERSION}") + + # llvm-cov prior version 3.5 seems to be not working with coverage + # evaluation tools, but these versions are compatible with the gcc + # gcov tool. + if(LLVM_VERSION VERSION_GREATER 3.4) + find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}" + "llvm-cov" HINTS ${COMPILER_PATH}) + mark_as_advanced(LLVM_COV_BIN) + + if (LLVM_COV_BIN) + find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS + ${CMAKE_MODULE_PATH}) + if (LLVM_COV_WRAPPER) + set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "") + + # set additional parameters + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV + "LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING + "Environment variables for llvm-cov-wrapper.") + mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV) + endif () + endif () + endif () + + if (NOT GCOV_BIN) + # Fall back to gcov binary if llvm-cov was not found or is + # incompatible. This is the default on OSX, but may crash on + # recent Linux versions. + find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH}) + endif () + endif () + + + if (GCOV_BIN) + set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING + "${LANG} gcov binary.") + + if (NOT CMAKE_REQUIRED_QUIET) + message("-- Found gcov evaluation for " + "${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}") + endif() + + unset(GCOV_BIN CACHE) + endif () + endif () +endforeach () + + + + +# Add a new global target for all gcov targets. This target could be used to +# generate the gcov files for the whole project instead of calling <TARGET>-gcov +# for each target. +if (NOT TARGET gcov) + add_custom_target(gcov) +endif (NOT TARGET gcov) + + + +# This function will add gcov evaluation for target <TNAME>. Only sources of +# this target will be evaluated and no dependencies will be added. It will call +# Gcov on any source file of <TNAME> once and store the gcov file in the same +# directory. +function (add_gcov_target TNAME) + set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) + + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + set(BUFFER "") + foreach(FILE ${SOURCES}) + get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH) + + # call gcov + add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov + COMMAND ${GCOV_ENV} ${GCOV_BIN} ${TDIR}/${FILE}.gcno > /dev/null + DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno + WORKING_DIRECTORY ${FILE_PATH} + ) + + list(APPEND BUFFER ${TDIR}/${FILE}.gcov) + endforeach() + + + # add target for gcov evaluation of <TNAME> + add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER}) + + # add evaluation target to the global gcov target. + add_dependencies(gcov ${TNAME}-gcov) +endfunction (add_gcov_target) diff --git a/cmake/modules/FindLcov.cmake b/cmake/modules/FindLcov.cmake new file mode 100644 index 0000000..3187a25 --- /dev/null +++ b/cmake/modules/FindLcov.cmake @@ -0,0 +1,353 @@ +# This file is part of CMake-codecov. +# +# CMake-codecov is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful,but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see +# +# http://www.gnu.org/licenses/ +# +# +# Copyright (c) +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Written by Alexander Haase, alexander.haase@rwth-aachen.de +# + + +# configuration +set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data") +set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init") +set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture") +set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html") + + + + +# Search for Gcov which is used by Lcov. +find_package(Gcov) + + + + +# This function will add lcov evaluation for target <TNAME>. Only sources of +# this target will be evaluated and no dependencies will be added. It will call +# geninfo on any source file of <TNAME> once and store the info file in the same +# directory. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (add_lcov_target TNAME) + if (LCOV_FOUND) + # capture initial coverage data + lcov_capture_initial_tgt(${TNAME}) + + # capture coverage data after execution + lcov_capture_tgt(${TNAME}) + endif () +endfunction (add_lcov_target) + + + + +# include required Modules +include(FindPackageHandleStandardArgs) + +# Search for required lcov binaries. +find_program(LCOV_BIN lcov) +find_program(GENINFO_BIN geninfo) +find_program(GENHTML_BIN genhtml) +find_package_handle_standard_args(lcov + REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN +) + +# enable genhtml C++ demangeling, if c++filt is found. +set(GENHTML_CPPFILT_FLAG "") +find_program(CPPFILT_BIN c++filt) +if (NOT CPPFILT_BIN STREQUAL "") + set(GENHTML_CPPFILT_FLAG "--demangle-cpp") +endif (NOT CPPFILT_BIN STREQUAL "") + +# enable no-external flag for lcov, if available. +if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG) + set(FLAG "") + execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP) + string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}") + if (GENINFO_RES) + set(FLAG "--no-external") + endif () + + set(GENINFO_EXTERN_FLAG "${FLAG}" + CACHE STRING "Geninfo flag to exclude system sources.") +endif () + +# If Lcov was not found, exit module now. +if (NOT LCOV_FOUND) + return() +endif (NOT LCOV_FOUND) + + + + +# Create directories to be used. +file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT}) +file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE}) + + +# This function will merge lcov files to a single target file. Additional lcov +# flags may be set with setting LCOV_EXTRA_FLAGS before calling this function. +function (lcov_merge_files OUTFILE ...) + # Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files. + list(REMOVE_AT ARGV 0) + + # Generate merged file. + string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}") + add_custom_command(OUTPUT "${OUTFILE}.raw" + COMMAND cat ${ARGV} > ${OUTFILE}.raw + DEPENDS ${ARGV} + COMMENT "Generating ${FILE_REL}" + ) + + add_custom_command(OUTPUT "${OUTFILE}" + COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE} + --base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS} + DEPENDS ${OUTFILE}.raw + COMMENT "Post-processing ${FILE_REL}" + ) +endfunction () + + + + +# Add a new global target to generate initial coverage reports for all targets. +# This target will be used to generate the global initial info file, which is +# used to gather even empty report data. +if (NOT TARGET lcov-capture-init) + add_custom_target(lcov-capture-init) + set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "") +endif (NOT TARGET lcov-capture-init) + + +# This function will add initial capture of coverage data for target <TNAME>, +# which is needed to get also data for objects, which were not loaded at +# execution time. It will call geninfo for every source file of <TNAME> once and +# store the info file in the same directory. +function (lcov_capture_initial_tgt TNAME) + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) + set(GENINFO_FILES "") + foreach(FILE ${SOURCES}) + # generate empty coverage files + set(OUTFILE "${TDIR}/${FILE}.info.init") + list(APPEND GENINFO_FILES ${OUTFILE}) + + add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN} + --quiet --base-directory ${PROJECT_SOURCE_DIR} --initial + --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE} + ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno + DEPENDS ${TNAME} + COMMENT "Capturing initial coverage data for ${FILE}" + ) + endforeach() + + # Concatenate all files generated by geninfo to a single file per target. + set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info") + set(LCOV_EXTRA_FLAGS "--initial") + lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) + add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE}) + + # add geninfo file generation to global lcov-geninfo target + add_dependencies(lcov-capture-init ${TNAME}-capture-init) + set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}" + "${OUTFILE}" CACHE INTERNAL "" + ) +endfunction (lcov_capture_initial_tgt) + + +# This function will generate the global info file for all targets. It has to be +# called after all other CMake functions in the root CMakeLists.txt file, to get +# a full list of all targets that generate coverage data. +function (lcov_capture_initial) + # Add a new target to merge the files of all targets. + set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info") + lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES}) + add_custom_target(lcov-geninfo-init ALL DEPENDS ${OUTFILE} + lcov-capture-init + ) +endfunction (lcov_capture_initial) + + + + +# Add a new global target to generate coverage reports for all targets. This +# target will be used to generate the global info file. +if (NOT TARGET lcov-capture) + add_custom_target(lcov-capture) + set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "") +endif (NOT TARGET lcov-capture) + + +# This function will add capture of coverage data for target <TNAME>, which is +# needed to get also data for objects, which were not loaded at execution time. +# It will call geninfo for every source file of <TNAME> once and store the info +# file in the same directory. +function (lcov_capture_tgt TNAME) + # We don't have to check, if the target has support for coverage, thus this + # will be checked by add_coverage_target in Findcoverage.cmake. Instead we + # have to determine which gcov binary to use. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(SOURCES "") + set(TCOMPILER "") + foreach (FILE ${TSOURCES}) + codecov_path_of_source(${FILE} FILE) + if (NOT "${FILE}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (NOT "${LANG}" STREQUAL "") + list(APPEND SOURCES "${FILE}") + set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + # If no gcov binary was found, coverage data can't be evaluated. + if (NOT GCOV_${TCOMPILER}_BIN) + message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") + return() + endif () + + set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") + set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") + + + set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) + set(GENINFO_FILES "") + foreach(FILE ${SOURCES}) + # Generate coverage files. If no .gcda file was generated during + # execution, the empty coverage file will be used instead. + set(OUTFILE "${TDIR}/${FILE}.info") + list(APPEND GENINFO_FILES ${OUTFILE}) + + add_custom_command(OUTPUT ${OUTFILE} + COMMAND test -f "${TDIR}/${FILE}.gcda" + && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory + ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN} + --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG} + ${TDIR}/${FILE}.gcda + || cp ${OUTFILE}.init ${OUTFILE} + DEPENDS ${TNAME} ${TNAME}-capture-init + COMMENT "Capturing coverage data for ${FILE}" + ) + endforeach() + + # Concatenate all files generated by geninfo to a single file per target. + set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info") + lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) + add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE}) + + # add geninfo file generation to global lcov-capture target + add_dependencies(lcov-capture ${TNAME}-geninfo) + set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL + "" + ) + + # Add target for generating html output for this target only. + file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME}) + add_custom_target(${TNAME}-genhtml + COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR} + --baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info + --output-directory ${LCOV_HTML_PATH}/${TNAME} + --title "${CMAKE_PROJECT_NAME} - target ${TNAME}" + ${GENHTML_CPPFILT_FLAG} ${OUTFILE} + DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init + ) +endfunction (lcov_capture_tgt) + + +# This function will generate the global info file for all targets. It has to be +# called after all other CMake functions in the root CMakeLists.txt file, to get +# a full list of all targets that generate coverage data. +function (lcov_capture) + # Add a new target to merge the files of all targets. + set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info") + lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES}) + add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture) + + # Add a new global target for all lcov targets. This target could be used to + # generate the lcov html output for the whole project instead of calling + # <TARGET>-geninfo and <TARGET>-genhtml for each target. It will also be + # used to generate a html site for all project data together instead of one + # for each target. + if (NOT TARGET lcov) + file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets) + add_custom_target(lcov + COMMAND ${GENHTML_BIN} --quiet --sort + --baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info + --output-directory ${LCOV_HTML_PATH}/all_targets + --title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}" + ${GENHTML_CPPFILT_FLAG} ${OUTFILE} + DEPENDS lcov-geninfo-init lcov-geninfo + ) + endif () +endfunction (lcov_capture) + + + + +# Add a new global target to generate the lcov html report for the whole project +# instead of calling <TARGET>-genhtml for each target (to create an own report +# for each target). Instead of the lcov target it does not require geninfo for +# all targets, so you have to call <TARGET>-geninfo to generate the info files +# the targets you'd like to have in your report or lcov-geninfo for generating +# info files for all targets before calling lcov-genhtml. +file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets) +if (NOT TARGET lcov-genhtml) + add_custom_target(lcov-genhtml + COMMAND ${GENHTML_BIN} + --quiet + --output-directory ${LCOV_HTML_PATH}/selected_targets + --title \"${CMAKE_PROJECT_NAME} - targets `find + ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name + \"all_targets.info\" -exec basename {} .info \\\;`\" + --prefix ${PROJECT_SOURCE_DIR} + --sort + ${GENHTML_CPPFILT_FLAG} + `find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name + \"all_targets.info\"` + ) +endif (NOT TARGET lcov-genhtml) diff --git a/cmake/modules/FindPackageVersionCheck.cmake b/cmake/modules/FindPackageVersionCheck.cmake new file mode 100644 index 0000000..711f2b6 --- /dev/null +++ b/cmake/modules/FindPackageVersionCheck.cmake @@ -0,0 +1,68 @@ +# FIND_PACKAGE_VERSION_CHECK(NAME (DEFAULT_MSG|"Custom failure message")) +# This function is intended to be used in FindXXX.cmake modules files. +# It handles NAME_FIND_VERSION and NAME_VERSION variables in a Module. +# +# Example: +# find_package(LibSSH 0.3.2) +# +# # check for the version and set it +# set(LibSSH_VERSION 0.3.0) +# find_package_version_check(LibSSH DEFAULT_MSG) +# +# +# Copyright (c) 2009 Andreas Schneider <mail@cynapses.org> +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. + +function(FIND_PACKAGE_VERSION_CHECK _NAME _FAIL_MSG) + string(TOUPPER ${_NAME} _NAME_UPPER) + set(_AGE "old") + + if(${_NAME}_FIND_VERSION_EXACT) + if (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + # exact version found + set(${_NAME_UPPER}_FOUND TRUE) + else (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + # exect version not found + set(${_NAME_UPPER}_FOUND FALSE) + # check if newer or older + if (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + set(_AGE "new") + else (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + set(_AGE "old") + endif (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + endif (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + else (${_NAME}_FIND_VERSION_EXACT) + if (${_NAME}_FIND_VERSION) + if (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + set(${_NAME_UPPER}_FOUND FALSE) + set(_AGE "old") + else (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + set(${_NAME_UPPER}_FOUND TRUE) + endif (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + endif (${_NAME}_FIND_VERSION) + endif(${_NAME}_FIND_VERSION_EXACT) + + if ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + if (${_NAME}_FIND_VERSION_EXACT) + set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, version ${${_NAME}_FIND_VERSION} is required.") + else (${_NAME}_FIND_VERSION_EXACT) + set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, at least version ${${_NAME}_FIND_VERSION} is required.") + endif (${_NAME}_FIND_VERSION_EXACT) + else ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + set(_FAIL_MESSAGE "${_FAIL_MSG}") + endif ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + + if (NOT ${_NAME_UPPER}_FOUND) + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${_FAIL_MESSAGE}") + else (${_NAME}_FIND_REQUIRED) + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${_FAIL_MESSAGE}") + endif (NOT ${_NAME}_FIND_QUIETLY) + endif (${_NAME}_FIND_REQUIRED) + endif (NOT ${_NAME_UPPER}_FOUND) + + set(${_NAME_UPPER}_FOUND ${${_NAME_UPPER}_FOUND} PARENT_SCOPE) +endfunction(FIND_PACKAGE_VERSION_CHECK) diff --git a/cmake/modules/Findcodecov.cmake b/cmake/modules/Findcodecov.cmake new file mode 100644 index 0000000..926f5f3 --- /dev/null +++ b/cmake/modules/Findcodecov.cmake @@ -0,0 +1,260 @@ +# This file is part of CMake-codecov. +# +# CMake-codecov is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful,but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see +# +# http://www.gnu.org/licenses/ +# +# +# Copyright (c) +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Written by Alexander Haase, alexander.haase@rwth-aachen.de +# + + +# Add an option to choose, if coverage should be enabled or not. If enabled +# marked targets will be build with coverage support and appropriate targets +# will be added. If disabled coverage will be ignored for *ALL* targets. +option(ENABLE_COVERAGE "Enable coverage build." OFF) + +set(COVERAGE_FLAG_CANDIDATES + # gcc and clang + "-O0 -g -fprofile-arcs -ftest-coverage" + + # gcc and clang fallback + "-O0 -g --coverage" +) + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. If coverage is disabled or not supported, this function will +# simply do nothing. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (add_coverage TNAME) + # only add coverage for target, if coverage is support and enabled. + if (ENABLE_COVERAGE) + add_coverage_target(${TNAME}) + endif () +endfunction (add_coverage) + + +# Add global target to gather coverage information after all targets have been +# added. Other evaluation functions could be added here, after checks for the +# specific module have been passed. +# +# Note: This function is only a wrapper to define this function always, even if +# coverage is not supported by the compiler or disabled. This function must +# be defined here, because the module will be exited, if there is no coverage +# support by the compiler or it is disabled by the user. +function (coverage_evaluate) + # add lcov evaluation + if (LCOV_FOUND) + lcov_capture_initial() + lcov_capture() + endif (LCOV_FOUND) +endfunction () + + +# Exit this module, if coverage is disabled. add_coverage is defined before this +# return, so this module can be exited now safely without breaking any build- +# scripts. +if (NOT ENABLE_COVERAGE) + return() +endif () + + + + +# Find the reuired flags foreach language. +set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) +set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) + +get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach (LANG ${ENABLED_LANGUAGES}) + # Coverage flags are not dependend on language, but the used compiler. So + # instead of searching flags foreach language, search flags foreach compiler + # used. + set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + if (NOT COVERAGE_${COMPILER}_FLAGS) + foreach (FLAG ${COVERAGE_FLAG_CANDIDATES}) + if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${FLAG}") + unset(COVERAGE_FLAG_DETECTED CACHE) + + if (${LANG} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) + + elseif (${LANG} STREQUAL "Fortran") + # CheckFortranCompilerFlag was introduced in CMake 3.x. To be + # compatible with older Cmake versions, we will check if this + # module is present before we use it. Otherwise we will define + # Fortran coverage support as not available. + include(CheckFortranCompilerFlag OPTIONAL + RESULT_VARIABLE INCLUDED) + if (INCLUDED) + check_fortran_compiler_flag("${FLAG}" + COVERAGE_FLAG_DETECTED) + elseif (NOT CMAKE_REQUIRED_QUIET) + message("-- Performing Test COVERAGE_FLAG_DETECTED") + message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed" + " (Check not supported)") + endif () + endif() + + if (COVERAGE_FLAG_DETECTED) + set(COVERAGE_${COMPILER}_FLAGS "${FLAG}" + CACHE STRING "${COMPILER} flags for code coverage.") + mark_as_advanced(COVERAGE_${COMPILER}_FLAGS) + break() + endif () + endforeach () + endif () +endforeach () + +set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) + + + + +# Helper function to get the language of a source file. +function (codecov_lang_of_source FILE RETURN_VAR) + get_filename_component(FILE_EXT "${FILE}" EXT) + string(TOLOWER "${FILE_EXT}" FILE_EXT) + string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) + if (NOT ${TEMP} EQUAL -1) + set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) + return() + endif () + endforeach() + + set(${RETURN_VAR} "" PARENT_SCOPE) +endfunction () + + +# Helper function to get the relative path of the source file destination path. +# This path is needed by FindGcov and FindLcov cmake files to locate the +# captured data. +function (codecov_path_of_source FILE RETURN_VAR) + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE}) + + # If expression was found, SOURCEFILE is a generator-expression for an + # object library. Currently we found no way to call this function automatic + # for the referenced target, so it must be called in the directoryso of the + # object library definition. + if (NOT "${_source}" STREQUAL "") + set(${RETURN_VAR} "" PARENT_SCOPE) + return() + endif () + + + string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}") + if(IS_ABSOLUTE ${FILE}) + file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) + endif() + + # get the right path for file + string(REPLACE ".." "__" PATH "${FILE}") + + set(${RETURN_VAR} "${PATH}" PARENT_SCOPE) +endfunction() + + + + +# Add coverage support for target ${TNAME} and register target for coverage +# evaluation. +function(add_coverage_target TNAME) + # Check if all sources for target use the same compiler. If a target uses + # e.g. C and Fortran mixed and uses different compilers (e.g. clang and + # gfortran) this can trigger huge problems, because different compilers may + # use different implementations for code coverage. + get_target_property(TSOURCES ${TNAME} SOURCES) + set(TARGET_COMPILER "") + set(ADDITIONAL_FILES "") + foreach (FILE ${TSOURCES}) + # If expression was found, FILE is a generator-expression for an object + # library. Object libraries will be ignored. + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) + if ("${_file}" STREQUAL "") + codecov_lang_of_source(${FILE} LANG) + if (LANG) + list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + + list(APPEND ADDITIONAL_FILES "${FILE}.gcno") + list(APPEND ADDITIONAL_FILES "${FILE}.gcda") + endif () + endif () + endforeach () + + list(REMOVE_DUPLICATES TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + + if (NUM_COMPILERS GREATER 1) + message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} because " + "it will be compiled by different compilers.") + return() + + elseif ((NUM_COMPILERS EQUAL 0) OR + (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS")) + message(AUTHOR_WARNING "Coverage disabled for target ${TNAME} " + "because there is no sanitizer available for target sources.") + return() + endif() + + + # enable coverage for target + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TNAME} APPEND_STRING + PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") + + + # Add gcov files generated by compiler to clean target. + set(CLEAN_FILES "") + foreach (FILE ${ADDITIONAL_FILES}) + codecov_path_of_source(${FILE} FILE) + list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}") + endforeach() + + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES + "${CLEAN_FILES}") + + + add_gcov_target(${TNAME}) + add_lcov_target(${TNAME}) +endfunction(add_coverage_target) + + + + +# Include modules for parsing the collected data and output it in a readable +# format (like gcov and lcov). +find_package(Gcov) +find_package(Lcov) diff --git a/cmake/modules/UseMultiArch.cmake b/cmake/modules/UseMultiArch.cmake new file mode 100644 index 0000000..06a4012 --- /dev/null +++ b/cmake/modules/UseMultiArch.cmake @@ -0,0 +1,42 @@ +# - Multiarch support in object code library directories +# +# This module sets the following variable +# CMAKE_INSTALL_LIBDIR to lib, lib64 or lib/x86_64-linux-gnu +# depending on the platform; use this path +# for platform-specific binaries. +# +# CMAKE_INSTALL_LIBDIR_NOARCH to lib or lib64 depending on the platform; +# use this path for architecture-independent +# files. +# +# Note that it will override the results of GNUInstallDirs if included after +# that module. + +# Fedora uses lib64/ for 64-bit systems, Debian uses lib/x86_64-linux-gnu; +# Fedora put module files in lib64/ too, but Debian uses lib/ for that +if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + # Debian or Ubuntu? + if (EXISTS "/etc/debian_version") + set (_libdir_def "lib/${CMAKE_LIBRARY_ARCHITECTURE}") + set (_libdir_noarch "lib") + else (EXISTS "/etc/debian_version") + # 64-bit system? + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set (_libdir_noarch "lib64") + else (CMAKE_SIZEOF_VOID_P EQUAL 8) + set (_libdir_noarch "lib") + endif (CMAKE_SIZEOF_VOID_P EQUAL 8) + set (_libdir_def "${_libdir_noarch}") + endif (EXISTS "/etc/debian_version") +else ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + set (_libdir_def "lib") + set (_libdir_noarch "lib") +endif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + +# let the user override if somewhere else is desirable +set (CMAKE_INSTALL_LIBDIR "${_libdir_def}" CACHE PATH "Object code libraries") +set (CMAKE_INSTALL_LIBDIR_NOARCH "${_libdir_noarch}" CACHE PATH "Architecture-independent library files") +mark_as_advanced ( + CMAKE_INSTALL_LIBDIR + CMAKE_INSTALL_LIBDIR_NOARCH + ) diff --git a/cmake/pkg-config.pc.cmake b/cmake/pkg-config.pc.cmake new file mode 100644 index 0000000..145b435 --- /dev/null +++ b/cmake/pkg-config.pc.cmake @@ -0,0 +1,10 @@ +prefix=${CMAKE_INSTALL_PREFIX} +includedir=${PKG_CONFIG_INCLUDEDIR} +libdir=${PKG_CONFIG_LIBDIR} + +Name: ${PROJECT_NAME} +Description: ${PROJECT_DESCRIPTION} +Version: ${LIBRARY_VERSION} +Requires: ${PKG_CONFIG_REQUIRES} +Libs: ${PKG_CONFIG_LIBS} +Cflags: ${PKG_CONFIG_CFLAGS} diff --git a/doxygen/Doxyfile.in b/doxygen/Doxyfile.in new file mode 100644 index 0000000..a9bef9e --- /dev/null +++ b/doxygen/Doxyfile.in @@ -0,0 +1,2332 @@ +# Doxyfile 1.8.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = RTRlib + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +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 = docs/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +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 = NO + +# 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 = NO + +# 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 = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +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 = NO + +# 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 = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +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 = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: 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 (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), 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 = NO + +# 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 = YES + +# 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 = NO + +# 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 = YES + +# 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 = NO + +# 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 = NO + +# 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 SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# 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 = YES + +# 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. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# 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 <section_label> ... \endif and \cond <section_label> +# ... \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 = NO + +# 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 = NO + +# 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 = NO + +# 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. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +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 = @CMAKE_CURRENT_SOURCE_DIR@/rtrlib/ @CMAKE_CURRENT_SOURCE_DIR@/doxygen/ + +# 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 \ + *.dox + +# 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 = */rtrlib/spki/hashtable/* @DOCS_EXCLUDE_PATTERN@ + +# 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 = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/examples/ + +# 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 = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/graphics/ + +# 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: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> 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 = YES + +# 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 = YES + +# 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 + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to 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 additional user-defined +# cascading style sheets that are 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 files to the output directory. +# Note: The order of the extra stylesheet files is of importance (e.g. the last +# stylesheet in the list overrules the setting of the previous ones in the +# list). 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 = YES + +# 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 + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = 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 QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# 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 + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# 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 = NO + +# 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://www.mathjax.org/mathjax + +# 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 <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX 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: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when enabling USE_PDFLATEX this option is only used for generating +# bitmaps for formulas in the HTML output, but not in the Makefile that is +# written to the output directory. +# The default file is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. To get the times font for +# instance you can specify +# EXTRA_PACKAGES=times +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string, +# for the replacement values of the other commands the user is refered to +# HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF 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: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's config +# file, i.e. a series of assignments. You only have to provide replacements, +# missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's config file. A template extensions file can be generated +# using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages 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: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages 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: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen +# Definitions (see http://autogen.sf.net) file that captures the structure of +# the code including all documentation. Note that this feature is still +# experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names +# in the source code. If set to NO only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external class will be listed in the +# class index. If set to NO only the inherited external classes will be listed. +# The default value is: NO. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in +# the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = NO + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif and svg. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. +# This tag requires that the tag HAVE_DOT is set to YES. + +PLANTUML_JAR_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES diff --git a/doxygen/examples/CMakeLists.txt b/doxygen/examples/CMakeLists.txt new file mode 100644 index 0000000..b58f7bf --- /dev/null +++ b/doxygen/examples/CMakeLists.txt @@ -0,0 +1,8 @@ +if(CMAKE_BUILD_TYPE STREQUAL Debug) + if(RTRLIB_HAVE_LIBSSH) + add_executable(rtr_mgr rtr_mgr.c) + target_link_libraries(rtr_mgr rtrlib) + add_executable(ssh_tr ssh_tr.c) + target_link_libraries(ssh_tr rtrlib_static) + endif(RTRLIB_HAVE_LIBSSH) +endif(CMAKE_BUILD_TYPE STREQUAL Debug) diff --git a/doxygen/examples/rtr_mgr.c b/doxygen/examples/rtr_mgr.c new file mode 100644 index 0000000..30d05b9 --- /dev/null +++ b/doxygen/examples/rtr_mgr.c @@ -0,0 +1,110 @@ +#include "rtrlib/rtrlib.h" + +#include <arpa/inet.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int main() +{ + //create a SSH transport socket + char ssh_host[] = "123.231.123.221"; + char ssh_user[] = "rpki_user"; + char ssh_hostkey[] = "/etc/rpki-rtr/hostkey"; + char ssh_privkey[] = "/etc/rpki-rtr/client.priv"; + struct tr_socket tr_ssh; + struct tr_ssh_config config = { + ssh_host, //IP + 22, //Port + NULL, //Source address + ssh_user, + ssh_hostkey, //Server hostkey + ssh_privkey, //Private key + NULL, // data + NULL, // new_socket() + 0, // connect timeout + NULL, // password + }; + tr_ssh_init(&config, &tr_ssh); + + //create a TCP transport socket + struct tr_socket tr_tcp; + char tcp_host[] = "rpki-validator.realmv6.org"; + char tcp_port[] = "8282"; + + struct tr_tcp_config tcp_config = { + tcp_host, //IP + tcp_port, //Port + NULL, //Source address + NULL, //data + NULL, //get_socket() + 0, // connect timeout + }; + tr_tcp_init(&tcp_config, &tr_tcp); + + //create 3 rtr_sockets and associate them with the transprort sockets + struct rtr_socket rtr_ssh, rtr_tcp; + rtr_ssh.tr_socket = &tr_ssh; + rtr_tcp.tr_socket = &tr_tcp; + + //create a rtr_mgr_group array with 2 elements + struct rtr_mgr_group groups[2]; + + //The first group contains both TCP RTR sockets + groups[0].sockets = malloc(sizeof(struct rtr_socket *)); + groups[0].sockets_len = 1; + groups[0].sockets[0] = &rtr_tcp; + groups[0].preference = 1; //Preference value of this group + + //The seconds group contains only the SSH RTR socket + groups[1].sockets = malloc(1 * sizeof(struct rtr_socket *)); + groups[1].sockets_len = 1; + groups[1].sockets[0] = &rtr_ssh; + groups[1].preference = 2; + + //create a rtr_mgr_config struct that stores the group + + //initialize all rtr_sockets in the server pool with the same settings + struct rtr_mgr_config *conf; + rtr_mgr_init(&conf, groups, 2, 30, 600, 600, NULL, NULL, NULL, NULL); + + //start the connection manager + rtr_mgr_start(conf); + + //wait till at least one rtr_mgr_group is fully synchronized with the server + while (!rtr_mgr_conf_in_sync(conf)) { + sleep(1); + } + + //validate the BGP-Route 10.10.0.0/24, origin ASN: 12345 + struct lrtr_ip_addr pref; + lrtr_ip_str_to_addr("10.10.0.0", &pref); + enum pfxv_state result; + const uint8_t mask = 24; + rtr_mgr_validate(conf, 12345, &pref, mask, &result); + + //output the result of the prefix validation above + //to showcase the returned states. + char buffer[INET_ADDRSTRLEN]; + lrtr_ip_addr_to_str(&pref, buffer, sizeof(buffer)); + + printf("RESULT: The prefix %s/%i ", buffer, mask); + switch (result) { + case BGP_PFXV_STATE_VALID: + printf("is valid.\n"); + break; + case BGP_PFXV_STATE_INVALID: + printf("is invalid.\n"); + break; + case BGP_PFXV_STATE_NOT_FOUND: + printf("was not found.\n"); + break; + default: + break; + } + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + free(groups[0].sockets); + free(groups[1].sockets); +} diff --git a/doxygen/examples/ssh_tr.c b/doxygen/examples/ssh_tr.c new file mode 100644 index 0000000..28876ca --- /dev/null +++ b/doxygen/examples/ssh_tr.c @@ -0,0 +1,18 @@ +#include "rtrlib/rtrlib.h" + +#include <stdlib.h> + +int main() +{ + struct tr_socket ssh_socket; + char ssh_host[] = "123.231.123.221"; + char ssh_user[] = "rpki_user"; + char ssh_hostkey[] = "/etc/rpki-rtr/hostkey"; + char ssh_privkey[] = "/etc/rpki-rtr/client.priv"; + + struct tr_ssh_config config = { + ssh_host, 22, NULL, ssh_user, ssh_hostkey, ssh_privkey, NULL, NULL, 0, NULL + }; + + tr_ssh_init(&config, &ssh_socket); +} diff --git a/doxygen/graphics/components.dia b/doxygen/graphics/components.dia Binary files differnew file mode 100644 index 0000000..3d06102 --- /dev/null +++ b/doxygen/graphics/components.dia diff --git a/doxygen/graphics/components.png b/doxygen/graphics/components.png Binary files differnew file mode 100644 index 0000000..6e7b6d7 --- /dev/null +++ b/doxygen/graphics/components.png diff --git a/doxygen/mainpage.dox b/doxygen/mainpage.dox new file mode 100644 index 0000000..6830a56 --- /dev/null +++ b/doxygen/mainpage.dox @@ -0,0 +1,53 @@ +/** +@mainpage RTRlib +This is the API documentation of the RTRlib, a C implementation of the +RPKI/Router Protocol client. + +@section Overview + +@par +The RTRlib follows a flexible design. The software architecture includes +different layers to simplify the extension or exchange of individual +parts.\n + +\n +\image html components.png +\n + +@par +The lowest layer of the architecture is built by the @ref mod_transport_h. +It allows for the implementation of different transport channels that +provide a common interface to exchange PDUs with the cache (i.e., the +RPKI-RTR server). The current version of the library supports unprotected +TCP and SSH.\n + +@par +On the top of the transport layer the @ref mod_rtr_h uses a transport +socket for RTR-specific data exchange with the RTR server. The RTR socket +implements the RPKI-RTR protocol, i.e., fetches validation records and +stores them in a prefix table data structure.\n + +@par +The @ref mod_pfx_h stores validated prefix origin data. The abstract data +structure provides a common interface to add and delete entries as well as +to verify a specific prefix. The library implements a Trie, +but can be extended to other data structures.\n + +@par +On the top of the modular architecture the @ref +mod_rtr_mgr_h maintains the connection to multiple +RTR servers. This includes failover mechanisms. It represents the main +interface for users of the library. + +@section RfcDrafts RFCs and Drafts +The functionality of this library is described by the following IETF RFCs +and drafts: +\li <a href="http://tools.ietf.org/html/rfc6810">The Resource Public Key +Infrastructure (RPKI) to Router Protocol (RFC 6810)</A> +\li <a href="http://tools.ietf.org/html/rfc6811"> BGP Prefix Origin +Validation (RFC 6811)</a> + +For a general overview of the topic have look at the homepage of the <a +href="http://datatracker.ietf.org/wg/sidr/charter/">IETF SIDR working +group</a>. +*/ diff --git a/redhat/SPECS/librtr.spec b/redhat/SPECS/librtr.spec new file mode 100644 index 0000000..6a3441b --- /dev/null +++ b/redhat/SPECS/librtr.spec @@ -0,0 +1,118 @@ +Name: librtr +Version: 0.8.0 +Release: 1%{?dist} +Summary: Small extensible RPKI-RTR-Client C library +Group: Development/Libraries +License: MIT +URL: http://rpki.realmv6.org/ +Source0: %{name}-%{version}.tar.gz +BuildRequires: binutils gcc tar cmake libssh-devel >= 0.5.0 doxygen +Requires: libssh >= 0.5.0 + +%description +RTRlib is an open-source C implementation of the RPKI/Router Protocol +client. The library allows one to fetch and store validated prefix origin +data from a RTR-cache and performs origin verification of prefixes. It +supports different types of transport sessions (e.g., SSH, unprotected TCP) +and is easily extendable. + +%package devel +Summary: Small extensible RPKI-RTR-Client C library. Development files +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} libssh-devel >= 0.5.0 + +%description devel +RTRlib is an open-source C implementation of the RPKI/Router Protocol +client. The library allows one to fetch and store validated prefix origin +data from a RTR-cache and performs origin verification of prefixes. It +supports different types of transport sessions (e.g., SSH, unprotected TCP) +and is easily extendable. +. +This package contains development files. + +%package doc +Summary: Small extensible RPKI-RTR-Client C library. Documentation +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} +BuildArch: noarch + +%description doc +RTRlib is an open-source C implementation of the RPKI/Router Protocol +client. The library allows one to fetch and store validated prefix origin +data from a RTR-cache and performs origin verification of prefixes. It +supports different types of transport sessions (e.g., SSH, unprotected TCP) +and is easily extendable. +. +This package contains documentation files. + +%package -n rtr-tools +Summary: RPKI-RTR command line tools +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description -n rtr-tools +Tools for the RTRlib +Rtrclient is command line that connects to an RPKI-RTR server and prints +protocol information and information about the fetched ROAs to the console. +rpki-rov is a command line tool that connects to an RPKI-RTR server and +allows to validate given IP prefixes and origin ASes. + +%prep +if [ ! -f %{SOURCE0} ]; then + # Build Source Tarball first + pushd `dirname %_topdir`; tar czf %{SOURCE0} --exclude-vcs --exclude=redhat . ; popd +fi +cd %{_topdir}/BUILD +rm -rf %{name}-%{version} +tar xzf %{SOURCE0} +/bin/chmod -Rf a+rX,u+w,g-w,o-w . + +%build +%cmake -D CMAKE_BUILD_TYPE=Release . +make %{?_smp_mflags} + +%install +%make_install +strip $RPM_BUILD_ROOT/usr/lib64/librtr.so.%{version} +strip $RPM_BUILD_ROOT/usr/bin/rpki-rov +strip $RPM_BUILD_ROOT/usr/bin/rtrclient +cp %{_topdir}/BUILD/CHANGELOG %{buildroot}/%{_docdir}/rtrlib/ +cp %{_topdir}/BUILD/LICENSE %{buildroot}/%{_docdir}/rtrlib/ + +%check +export LD_LIBRARY_PATH=.; make test + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%{_libdir}/lib*.so.0 +%attr(755,root,root) %{_libdir}/lib*.so.0.* +%doc CHANGELOG +%doc LICENSE + +%files devel +%{_libdir}/lib*.so +%attr(644,root,root) %{_libdir}/pkgconfig/rtrlib.pc +%{_includedir}/rtrlib +%doc CHANGELOG +%doc LICENSE + +%files doc +%{_docdir}/rtrlib + +%files -n rtr-tools +%attr(755,root,root) %{_bindir}/rtrclient +%attr(755,root,root) %{_bindir}/rpki-rov +%attr(644,root,root) %{_mandir}/man1/rtrclient.1.gz +%attr(644,root,root) %{_mandir}/man1/rpki-rov.1.gz +%doc CHANGELOG +%doc LICENSE + +%changelog +* Sun Mar 15 2020 Martin Winter <mwinter@opensourcerouting.org> - %{version}-%{release} +- Update RPM spec changelog to fix changelog error + +* Thu Dec 14 2017 Martin Winter <mwinter@opensourcerouting.org> - 0.5.0 +- RPM Packaging added diff --git a/rtrlib/lib/alloc_utils.c b/rtrlib/lib/alloc_utils.c new file mode 100644 index 0000000..e3d6831 --- /dev/null +++ b/rtrlib/lib/alloc_utils.c @@ -0,0 +1,81 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "alloc_utils_private.h" + +#include "rtrlib/rtrlib_export_private.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +static void *(*MALLOC_PTR)(size_t size) = malloc; +static void *(*REALLOC_PTR)(void *ptr, size_t size) = realloc; +static void (*FREE_PTR)(void *ptr) = free; + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void lrtr_set_alloc_functions(void *(*malloc_function)(size_t size), + void *(*realloc_function)(void *ptr, size_t size), + void(free_function)(void *ptr)) +{ + MALLOC_PTR = malloc_function; + REALLOC_PTR = realloc_function; + FREE_PTR = free_function; +} + +inline void *lrtr_malloc(size_t size) +{ + return MALLOC_PTR(size); +} + +/* cppcheck-suppress unusedFunction */ +void *lrtr_calloc(size_t nmemb, size_t size) +{ + int bytes = 0; + +#if __GNUC__ >= 5 + if (__builtin_mul_overflow(nmemb, size, &bytes)) { + errno = ENOMEM; + return 0; + } +#else + if (size && nmemb > (size_t)-1 / size) { + errno = ENOMEM; + return 0; + } + bytes = size * nmemb; +#endif + void *p = lrtr_malloc(bytes); + + if (!p) + return p; + + return memset(p, 0, bytes); +} + +inline void lrtr_free(void *ptr) +{ + return FREE_PTR(ptr); +} + +inline void *lrtr_realloc(void *ptr, size_t size) +{ + return REALLOC_PTR(ptr, size); +} + +char *lrtr_strdup(const char *string) +{ + assert(string); + + size_t length = strlen(string) + 1; + char *new_string = lrtr_malloc(length); + + return new_string ? memcpy(new_string, string, length) : NULL; +} diff --git a/rtrlib/lib/alloc_utils.h b/rtrlib/lib/alloc_utils.h new file mode 100644 index 0000000..b265915 --- /dev/null +++ b/rtrlib/lib/alloc_utils.h @@ -0,0 +1,25 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_ALLOC_UTILS_H +#define LRTR_ALLOC_UTILS_H + +#include <stdlib.h> + +/** + * @brief Sets custom malloc, realloc and free function + * that is used throughout rtrlib. + * @param[in] Pointer to malloc function + * @param[in] Pointer to realloc function + * @param[in] Pointer to free function + */ +void lrtr_set_alloc_functions(void *(*malloc_function)(size_t size), void *(*realloc_function)(void *ptr, size_t size), + void (*free_function)(void *ptr)); + +#endif diff --git a/rtrlib/lib/alloc_utils_private.h b/rtrlib/lib/alloc_utils_private.h new file mode 100644 index 0000000..b6c67cc --- /dev/null +++ b/rtrlib/lib/alloc_utils_private.h @@ -0,0 +1,34 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_ALLOC_UTILS_PRIVATE_H +#define LRTR_ALLOC_UTILS_PRIVATE_H + +#include "alloc_utils.h" + +#include <stdlib.h> + +void *lrtr_malloc(size_t size); + +void *lrtr_calloc(size_t nmemb, size_t size); + +void lrtr_free(void *ptr); + +void *lrtr_realloc(void *ptr, size_t size); + +/** + * @brief Duplicates a string + * @pre string != NULL + * @param[in] string + * @returns Duplicated string + * @returns NULL on error + */ +char *lrtr_strdup(const char *string); + +#endif diff --git a/rtrlib/lib/convert_byte_order.c b/rtrlib/lib/convert_byte_order.c new file mode 100644 index 0000000..402c141 --- /dev/null +++ b/rtrlib/lib/convert_byte_order.c @@ -0,0 +1,34 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "convert_byte_order_private.h" + +#include <arpa/inet.h> +#include <assert.h> +#include <inttypes.h> + +uint16_t lrtr_convert_short(const enum target_byte_order tbo, const uint16_t value) +{ + if (tbo == TO_NETWORK_BYTE_ORDER) + return htons(value); + else if (tbo == TO_HOST_HOST_BYTE_ORDER) + return ntohs(value); + + assert(0); +} + +uint32_t lrtr_convert_long(const enum target_byte_order tbo, const uint32_t value) +{ + if (tbo == TO_NETWORK_BYTE_ORDER) + return htonl(value); + else if (tbo == TO_HOST_HOST_BYTE_ORDER) + return ntohl(value); + + assert(0); +} diff --git a/rtrlib/lib/convert_byte_order_private.h b/rtrlib/lib/convert_byte_order_private.h new file mode 100644 index 0000000..839f82b --- /dev/null +++ b/rtrlib/lib/convert_byte_order_private.h @@ -0,0 +1,39 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_CONVERT_BYTE_ORDER_PRIVATE_H +#define LRTR_CONVERT_BYTE_ORDER_PRIVATE_H + +#include <inttypes.h> + +/** + * @brief Target byte order for conversion. + */ +enum target_byte_order { + TO_NETWORK_BYTE_ORDER, + TO_HOST_HOST_BYTE_ORDER, +}; + +/** + * Converts the passed short value to the given target byte order. + * @param[in] tbo Target byte order. + * @param[in] value Input (uint16_t) for conversion. + * @result Converted uint16_t value. + */ +uint16_t lrtr_convert_short(const enum target_byte_order tbo, const uint16_t value); + +/** + * Converts the passed long value to the given target byte order. + * @param[in] tbo Target byte order. + * @param[in] value Input (uint32_t) for conversion. + * @result Converted uint32_t value. + */ +uint32_t lrtr_convert_long(const enum target_byte_order tbo, const uint32_t value); + +#endif /* LRTR_CONVERT_BYTE_ORDER_H */ diff --git a/rtrlib/lib/ip.c b/rtrlib/lib/ip.c new file mode 100644 index 0000000..39df774 --- /dev/null +++ b/rtrlib/lib/ip.c @@ -0,0 +1,79 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ip_private.h" + +#include "rtrlib/rtrlib_export_private.h" + +#include <stdbool.h> +#include <string.h> + +bool lrtr_ip_addr_is_zero(const struct lrtr_ip_addr prefix) +{ + if (prefix.ver == LRTR_IPV6) { + if (prefix.u.addr6.addr[0] == 0 && prefix.u.addr6.addr[1] == 0 && prefix.u.addr6.addr[2] == 0 && + prefix.u.addr6.addr[3] == 0) { + return true; + } + } else if (prefix.u.addr4.addr == 0) { + return true; + } + + return false; +} + +struct lrtr_ip_addr lrtr_ip_addr_get_bits(const struct lrtr_ip_addr *val, const uint8_t from, const uint8_t number) +{ + struct lrtr_ip_addr result; + + if (val->ver == LRTR_IPV6) { + result.ver = LRTR_IPV6; + result.u.addr6 = lrtr_ipv6_get_bits(&(val->u.addr6), from, number); + } else { + result.ver = LRTR_IPV4; + result.u.addr4 = lrtr_ipv4_get_bits(&(val->u.addr4), from, number); + } + return result; +} + +RTRLIB_EXPORT bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b) +{ + if (a.ver != b.ver) + return false; + if (a.ver == LRTR_IPV6) + return lrtr_ipv6_addr_equal(&(a.u.addr6), &(b.u.addr6)); + return lrtr_ipv4_addr_equal(&(a.u.addr4), &(b.u.addr4)); +} + +RTRLIB_EXPORT int lrtr_ip_addr_to_str(const struct lrtr_ip_addr *ip, char *str, const unsigned int len) +{ + if (ip->ver == LRTR_IPV6) + return lrtr_ipv6_addr_to_str(&(ip->u.addr6), str, len); + return lrtr_ipv4_addr_to_str(&(ip->u.addr4), str, len); +} + +RTRLIB_EXPORT int lrtr_ip_str_to_addr(const char *str, struct lrtr_ip_addr *ip) +{ + if (!strchr(str, ':')) { + ip->ver = LRTR_IPV4; + return lrtr_ipv4_str_to_addr(str, &(ip->u.addr4)); + } + ip->ver = LRTR_IPV6; + return lrtr_ipv6_str_to_addr(str, &(ip->u.addr6)); +} + +// cppcheck-suppress unusedFunction +RTRLIB_EXPORT bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2) +{ + struct lrtr_ip_addr tmp; + + if (lrtr_ip_str_to_addr(addr2, &tmp) == -1) + return false; + return lrtr_ip_addr_equal(*addr1, tmp); +} diff --git a/rtrlib/lib/ip.h b/rtrlib/lib/ip.h new file mode 100644 index 0000000..df3bb06 --- /dev/null +++ b/rtrlib/lib/ip.h @@ -0,0 +1,87 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IP_PUBLIC_H +#define LRTR_IP_PUBLIC_H + +#include "rtrlib/lib/ipv4.h" +#include "rtrlib/lib/ipv6.h" + +#include <stdbool.h> +/** + * @defgroup util_h Utility functions + * @{ + */ + +/** + * @brief Version of the IP protocol. + */ +enum lrtr_ip_version { + /** IPV4 */ + LRTR_IPV4, + + /** LRTR_IPV6 */ + LRTR_IPV6 +}; + +/** + * @brief The lrtr_ip_addr struct stores a IPv4 or IPv6 address in host byte order. + * @param ver Specifies the type of the stored address. + * @param u Union holding a lrtr_ipv4_addr or lrtr_ipv6_addr. + */ +struct lrtr_ip_addr { + enum lrtr_ip_version ver; + union { + struct lrtr_ipv4_addr addr4; + struct lrtr_ipv6_addr addr6; + } u; +}; + +/** + * Converts the passed lrtr_ip_addr struct to string representation. + * @param[in] ip lrtr_ip_addr + * @param[out] str Pointer to a char array. + * The array must be at least INET_ADDRSTRLEN bytes long if the passed lrtr_ip_addr stores an IPv4 address. + * If lrtr_ip_addr stores an IPv6 address, str must be at least INET6_ADDRSTRLEN bytes long. + * @param[in] len Length of the str array. + * @result 0 On success. + * @result -1 On error. + */ +int lrtr_ip_addr_to_str(const struct lrtr_ip_addr *ip, char *str, const unsigned int len); + +/** + * Converts the passed IP address in string representation to an lrtr_ip_addr. + * @param[in] str Pointer to a Null terminated char array. + * @param[out] ip Pointer to a lrtr_ip_addr struct. + * @result 0 On success. + * @result -1 On error. + */ +int lrtr_ip_str_to_addr(const char *str, struct lrtr_ip_addr *ip); + +/** + * + * @brief Checks if two lrtr_ip_addr structs are equal. + * @param[in] a lrtr_ip_addr + * @param[in] b lrtr_ip_addr + * @return true If a == b. + * @return false If a != b. + */ +bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b); + +/** + * Compares addr1 in the lrtr_ip_addr struct with addr2 in string representation. + * @param[in] addr1 lrtr_ip_addr + * @param[in] addr2 IP-address as string + * @return true If a == b + * @return false If a != b + */ +bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2); + +#endif +/** @} */ diff --git a/rtrlib/lib/ip_private.h b/rtrlib/lib/ip_private.h new file mode 100644 index 0000000..623fd96 --- /dev/null +++ b/rtrlib/lib/ip_private.h @@ -0,0 +1,58 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IP_PRIVATE_H +#define LRTR_IP_PRIVATE_H + +#include "ip.h" + +#include "rtrlib/lib/ipv4_private.h" +#include "rtrlib/lib/ipv6_private.h" + +/** + * @brief Detects if the lrtr_ip_addr only contains 0 bits. + * @param[in] lrtr_ip_addr + * @returns true If the saved lrtr_ip_addr is 0. + * @returns false If the saved lrtr_ip_addr isn't 0. + */ +bool lrtr_ip_addr_is_zero(const struct lrtr_ip_addr); + +/** + * @brief Extracts number bits from the passed lrtr_ip_addr, starting at bit number from. The bit with the highest + * significance is bit 0. All bits that aren't in the specified range will be 0. + * @param[in] val lrtr_ip_addr + * @param[in] from Position of the first bit that is extracted. + * @param[in] number How many bits will be extracted. + * @returns An lrtr_ipv_addr, where all bits that aren't in the specified range are set to 0. + */ +struct lrtr_ip_addr lrtr_ip_addr_get_bits(const struct lrtr_ip_addr *val, const uint8_t from, const uint8_t number); + +/** + * @defgroup util_h Utility functions + * @{ + * + * @brief Checks if two lrtr_ip_addr structs are equal. + * @param[in] a lrtr_ip_addr + * @param[in] b lrtr_ip_addr + * @return true If a == b. + * @return false If a != b. + */ +bool lrtr_ip_addr_equal(const struct lrtr_ip_addr a, const struct lrtr_ip_addr b); + +/** + * Compares addr1 in the lrtr_ip_addr struct with addr2 in string representation. + * @param[in] addr1 lrtr_ip_addr + * @param[in] addr2 IP-address as string + * @return true If a == b + * @return false If a != b + */ +bool lrtr_ip_str_cmp(const struct lrtr_ip_addr *addr1, const char *addr2); + +#endif +/** @} */ diff --git a/rtrlib/lib/ipv4.c b/rtrlib/lib/ipv4.c new file mode 100644 index 0000000..d7f10e1 --- /dev/null +++ b/rtrlib/lib/ipv4.c @@ -0,0 +1,64 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ipv4_private.h" + +#include "rtrlib/lib/convert_byte_order_private.h" +#include "rtrlib/lib/utils_private.h" + +#include <assert.h> +#include <stdio.h> + +struct lrtr_ipv4_addr lrtr_ipv4_get_bits(const struct lrtr_ipv4_addr *val, const uint8_t from, const uint8_t quantity) +{ + struct lrtr_ipv4_addr result; + + result.addr = lrtr_get_bits(val->addr, from, quantity); + return result; +} + +int lrtr_ipv4_addr_to_str(const struct lrtr_ipv4_addr *ip, char *str, unsigned int len) +{ + uint8_t buff[4]; + + buff[0] = ip->addr >> 24 & 0xff; + buff[1] = ip->addr >> 16 & 0xff; + buff[2] = ip->addr >> 8 & 0xff; + buff[3] = ip->addr & 0xff; + + if (snprintf(str, len, "%hhu.%hhu.%hhu.%hhu", buff[0], buff[1], buff[2], buff[3]) < 0) + return -1; + + return 0; +} + +int lrtr_ipv4_str_to_addr(const char *str, struct lrtr_ipv4_addr *ip) +{ + uint8_t buff[4]; + + if (sscanf(str, "%3hhu.%3hhu.%3hhu.%3hhu", &buff[0], &buff[1], &buff[2], &buff[3]) != 4) + return -1; + + ip->addr = buff[0] << 24 | buff[1] << 16 | buff[2] << 8 | buff[3]; + + return 0; +} + +bool lrtr_ipv4_addr_equal(const struct lrtr_ipv4_addr *a, const struct lrtr_ipv4_addr *b) +{ + if (a->addr == b->addr) + return true; + + return false; +} + +void lrtr_ipv4_addr_convert_byte_order(const uint32_t src, uint32_t *dest, const enum target_byte_order tbo) +{ + *dest = lrtr_convert_long(tbo, src); +} diff --git a/rtrlib/lib/ipv4.h b/rtrlib/lib/ipv4.h new file mode 100644 index 0000000..9aff5aa --- /dev/null +++ b/rtrlib/lib/ipv4.h @@ -0,0 +1,23 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IPV4_H +#define LRTR_IPV4_H + +#include <stdint.h> + +/** + * @brief Struct storing an IPv4 address in host byte order. + * @param addr The IPv4 address. + */ +struct lrtr_ipv4_addr { + uint32_t addr; +}; + +#endif diff --git a/rtrlib/lib/ipv4_private.h b/rtrlib/lib/ipv4_private.h new file mode 100644 index 0000000..73d0170 --- /dev/null +++ b/rtrlib/lib/ipv4_private.h @@ -0,0 +1,79 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IPV4_PRIVATE_H +#define LRTR_IPV4_PRIVATE_H + +#include "ipv4.h" + +#include "rtrlib/lib/convert_byte_order_private.h" + +#include <inttypes.h> +#include <stdbool.h> + +/** + * @brief Extracts number bits from the passed ipv4_addr, + * + * Starting at bit number from. The bit with the highest significance is bit 0. + * All bits that aren't in the specified range will be 0. + * + * @param[in] val ipv4_addr + * @param[in] from Position of the first bit that is extracted. + * @param[in] number How many bits will be extracted. + * + * @returns An ipv4_addr, with all bits not in the specified range set to 0. + */ +struct lrtr_ipv4_addr lrtr_ipv4_get_bits(const struct lrtr_ipv4_addr *val, const uint8_t from, const uint8_t number); + +/** + * @brief Converts ab IPv4 address from string to ipv4_addr struct. + * + * @param[in] str Pointer to a string buffer. + * @param[out] ip ipv4_addr + * + * @result 0 on success + * @result -1 on error + */ +int lrtr_ipv4_str_to_addr(const char *str, struct lrtr_ipv4_addr *ip); + +/** + * @brief Converts an ipv4_addr struct to its string representation. + * + * @param[in] ip ipv4_addr + * @param[out] str Pointer to a string buffer, of at least INET_ADDRSTRLEN. + * @param[in] length of *str + * + * @result 0 on success + * @result -1 on error + */ +int lrtr_ipv4_addr_to_str(const struct lrtr_ipv4_addr *ip, char *str, const unsigned int len); + +/** + * @brief Compares two ipv4_addr structs. + * + * @param[in] a ipv4_addr + * @param[in] b ipv4_addr + * + * @return true if a == b + * @return false if a != b + */ +bool lrtr_ipv4_addr_equal(const struct lrtr_ipv4_addr *a, const struct lrtr_ipv4_addr *b); + +/** + * @ingroup util_h[{ + * @brief Converts the passed IPv4 address to given byte order. + * + * @param[in] src IPv4 address in source byte order. + * @param[out] dest IPv4 address in target byte order. + * @param[in] tbo Target byte order for address conversion. + * } + */ +void lrtr_ipv4_addr_convert_byte_order(const uint32_t src, uint32_t *dest, const enum target_byte_order tbo); + +#endif diff --git a/rtrlib/lib/ipv6.c b/rtrlib/lib/ipv6.c new file mode 100644 index 0000000..5034c3c --- /dev/null +++ b/rtrlib/lib/ipv6.c @@ -0,0 +1,220 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ipv6_private.h" + +#include "rtrlib/lib/convert_byte_order_private.h" +#include "rtrlib/lib/ipv4_private.h" +#include "rtrlib/lib/utils_private.h" + +#include <arpa/inet.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> + +inline bool lrtr_ipv6_addr_equal(const struct lrtr_ipv6_addr *a, const struct lrtr_ipv6_addr *b) +{ + if (a->addr[0] == b->addr[0] && a->addr[1] == b->addr[1] && a->addr[2] == b->addr[2] && + a->addr[3] == b->addr[3]) + return true; + return false; +} + +struct lrtr_ipv6_addr lrtr_ipv6_get_bits(const struct lrtr_ipv6_addr *val, const uint8_t first_bit, + const uint8_t quantity) +{ + assert(first_bit <= 127); + assert(quantity <= 128); + assert(first_bit + quantity <= 128); + + // if no bytes get extracted the result has to be 0 + struct lrtr_ipv6_addr result; + + memset(&result, 0, sizeof(result)); + + uint8_t bits_left = quantity; + + if (first_bit <= 31) { + const uint8_t q = quantity > 32 ? 32 : quantity; + + assert(bits_left >= q); + bits_left -= q; + result.addr[0] = lrtr_get_bits(val->addr[0], first_bit, q); + } + + if ((first_bit <= 63) && ((first_bit + quantity) > 32)) { + const uint8_t fr = first_bit < 32 ? 0 : first_bit - 32; + const uint8_t q = bits_left > 32 ? 32 : bits_left; + + assert(bits_left >= q); + bits_left -= q; + result.addr[1] = lrtr_get_bits(val->addr[1], fr, q); + } + + if ((first_bit <= 95) && ((first_bit + quantity) > 64)) { + const uint8_t fr = first_bit < 64 ? 0 : first_bit - 64; + const uint8_t q = bits_left > 32 ? 32 : bits_left; + + assert(bits_left >= q); + bits_left -= q; + result.addr[2] = lrtr_get_bits(val->addr[2], fr, q); + } + + if ((first_bit <= 127) && ((first_bit + quantity) > 96)) { + const uint8_t fr = first_bit < 96 ? 0 : first_bit - 127; + const uint8_t q = bits_left > 32 ? 32 : bits_left; + + assert(bits_left >= q); + result.addr[3] = lrtr_get_bits(val->addr[3], fr, q); + } + return result; +} + +/* + * This function was copied from the bird routing daemon's ip_pton(..) function. + */ +int lrtr_ipv6_str_to_addr(const char *a, struct lrtr_ipv6_addr *ip) +{ + uint32_t *o = ip->addr; + uint16_t words[8]; + int i, j, k, l, hfil; + const char *start; + + if (a[0] == ':') { /* Leading :: */ + if (a[1] != ':') + return -1; + a++; + } + hfil = -1; + i = 0; + while (*a) { + if (*a == ':') { /* :: */ + if (hfil >= 0) + return -1; + hfil = i; + a++; + continue; + } + j = 0; + l = 0; + start = a; + for (;;) { + if (*a >= '0' && *a <= '9') + k = *a++ - '0'; + else if (*a >= 'A' && *a <= 'F') + k = *a++ - 'A' + 10; + else if (*a >= 'a' && *a <= 'f') + k = *a++ - 'a' + 10; + else + break; + j = (j << 4) + k; + if (j >= 0x10000 || ++l > 4) + return -1; + } + if (*a == ':' && a[1]) { + a++; + } else if (*a == '.' && (i == 6 || (i < 6 && hfil >= 0))) { /* Embedded IPv4 address */ + struct lrtr_ipv4_addr addr4; + + if (lrtr_ipv4_str_to_addr(start, &addr4) == -1) + return -1; + words[i++] = addr4.addr >> 16; + words[i++] = addr4.addr; + break; + } else if (*a) { + return -1; + } + if (i >= 8) + return -1; + words[i++] = j; + } + + /* Replace :: with an appropriate quantity of zeros */ + if (hfil >= 0) { + j = 8 - i; + for (i = 7; i - j >= hfil; i--) + words[i] = words[i - j]; + for (; i >= hfil; i--) + words[i] = 0; + } + + /* Convert the address to lrtr_ip_addr format */ + for (i = 0; i < 4; i++) + o[i] = (words[2 * i] << 16) | words[2 * i + 1]; + return 0; +} + +/* + * This function was copied from the bird routing daemon's ip_ntop(..) function. + */ +int lrtr_ipv6_addr_to_str(const struct lrtr_ipv6_addr *ip_addr, char *b, const unsigned int len) +{ + if (len < INET6_ADDRSTRLEN) + return -1; + const uint32_t *a = ip_addr->addr; + uint16_t words[8]; + int bestpos = 0; + int bestlen = 0; + int curpos = 0; + int curlen = 0; + int i; + + /* First of all, preprocess the address and find the longest run of zeros */ + for (i = 0; i < 8; i++) { + uint32_t x = a[i / 2]; + + words[i] = ((i % 2) ? x : (x >> 16)) & 0xffff; + if (words[i]) { + curlen = 0; + } else { + if (!curlen) + curpos = i; + curlen++; + if (curlen > bestlen) { + bestpos = curpos; + bestlen = curlen; + } + } + } + if (bestlen < 2) + bestpos = -1; + + /* Is it an encapsulated IPv4 address? */ + if (!bestpos && ((bestlen == 5 && a[2] == 0xffff) || bestlen == 6)) + // if (!bestpos && ((bestlen == 5 && (a[2] == 0xffff)) || bestlen == 6)) + { + uint32_t x = a[3]; + + b += sprintf(b, "::%s%d.%d.%d.%d", a[2] ? "ffff:" : "", ((x >> 24) & 0xff), ((x >> 16) & 0xff), + ((x >> 8) & 0xff), (x & 0xff)); + return 0; + } + + /* Normal IPv6 formatting, compress the largest sequence of zeros */ + for (i = 0; i < 8; i++) { + if (i == bestpos) { + i += bestlen - 1; + *b++ = ':'; + if (i == 7) + *b++ = ':'; + } else { + if (i) + *b++ = ':'; + b += sprintf(b, "%x", words[i]); + } + } + *b = '\0'; + return 0; +} + +void lrtr_ipv6_addr_convert_byte_order(const uint32_t *src, uint32_t *dest, const enum target_byte_order tbo) +{ + for (int i = 0; i < 4; i++) + dest[i] = lrtr_convert_long(tbo, src[i]); +} diff --git a/rtrlib/lib/ipv6.h b/rtrlib/lib/ipv6.h new file mode 100644 index 0000000..6be7485 --- /dev/null +++ b/rtrlib/lib/ipv6.h @@ -0,0 +1,22 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IPV6_PUBLIC_H +#define LRTR_IPV6_PUBLIC_H + +#include <stdint.h> + +/** + * @brief Struct holding an IPv6 address in host byte order. + */ +struct lrtr_ipv6_addr { + uint32_t addr[4]; /**< The IPv6 address. */ +}; + +#endif /* LRTR_IPV6_PUBLIC_H */ diff --git a/rtrlib/lib/ipv6_private.h b/rtrlib/lib/ipv6_private.h new file mode 100644 index 0000000..1007e73 --- /dev/null +++ b/rtrlib/lib/ipv6_private.h @@ -0,0 +1,81 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_IPV6_PRIVATE_H +#define LRTR_IPV6_PRIVATE_H + +#include "ipv6.h" + +#include "rtrlib/lib/convert_byte_order_private.h" + +#include <stdbool.h> +#include <stdint.h> +#include <sys/types.h> + +/** + * @brief Compares two lrtr_ipv6_addr structs + * + * @param[in] a lrtr_ipv6_addr + * @param[in] b lrtr_ipv6_addr + * + * @return true if a == b + * @return false if a != b + */ +bool lrtr_ipv6_addr_equal(const struct lrtr_ipv6_addr *a, const struct lrtr_ipv6_addr *b); + +/** + * @brief Extracts quantity bits from an IPv6 address. + * + * The bit with the highest significance is bit 0. All bits that aren't in the + * specified range will be 0. + * + * @param[in] val ipv6_addr + * @param[in] first_bit Position of the first bit that is extracted, inclusive. + * @param[in] quantity How many bits will be extracted. + * + * @returns ipv6_addr, with all bits not in specified range set to 0. + */ +struct lrtr_ipv6_addr lrtr_ipv6_get_bits(const struct lrtr_ipv6_addr *val, const uint8_t first_bit, + const uint8_t quantity); + +/** + * @brief Converts the passed ipv6_addr to string representation + * + * @param[in] ip_addr Pointer to an IPv6 address + * @param[out] str Pointer to string buf, at least INET6_ADDRSTRLEN bytes. + * @param[in] len Length of string buffer @p str + * + * @result 0 on success + * @result -1 on error + */ +int lrtr_ipv6_addr_to_str(const struct lrtr_ipv6_addr *ip, char *str, const unsigned int len); + +/** + * @brief Converts the passed IPv6 address string in to lrtr_ipv6_addr struct. + * + * @param[in] str Pointer to a string buffer + * @param[out] ip Pointer to lrtr_ipv6_addr + * + * @result 0 on success + * @result -1 on error + */ +int lrtr_ipv6_str_to_addr(const char *str, struct lrtr_ipv6_addr *ip); + +/** + * @ingroup util_h + * @{ + * @brief Converts the passed IPv6 address to given byte order. + * + * @param[in] src IPv6 address (uint32_t array) in source byte order. + * @param[out] dest IPv6 address (uint32_t array) in target byte order. + * @param[in] tbo Target byte order for address conversion. + */ +void lrtr_ipv6_addr_convert_byte_order(const uint32_t *src, uint32_t *dest, const enum target_byte_order tbo); +/** @} */ +#endif /* LRTR_IPV6_H */ diff --git a/rtrlib/lib/log.c b/rtrlib/lib/log.c new file mode 100644 index 0000000..6ceaaa3 --- /dev/null +++ b/rtrlib/lib/log.c @@ -0,0 +1,48 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "log_private.h" + +#include <arpa/inet.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/time.h> +#include <time.h> + +void lrtr_dbg(const char *frmt, ...) +{ +#ifndef NDEBUG + va_list argptr; + struct timeval tv; + struct timezone tz; + + va_start(argptr, frmt); + + bool fail = true; + + if (gettimeofday(&tv, &tz) == 0) { + struct tm tm; + + if (localtime_r(&tv.tv_sec, &tm)) { + fprintf(stderr, "(%04d/%02d/%02d %02d:%02d:%02d:%06ld): ", tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec); + fail = false; + } + } + + if (fail) + fprintf(stderr, "(%jd): ", (intmax_t)time(0)); + + vfprintf(stderr, frmt, argptr); + fprintf(stderr, "\n"); + va_end(argptr); +#endif +} diff --git a/rtrlib/lib/log_private.h b/rtrlib/lib/log_private.h new file mode 100644 index 0000000..e33ebf3 --- /dev/null +++ b/rtrlib/lib/log_private.h @@ -0,0 +1,19 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_LOG_PRIVATE_H +#define LRTR_LOG_PRIVATE_H + +/** + * @brief Writes a message to stdout if NDEBUG isn't defined. + * @param[in] frmt log message in printf format style. + */ +void lrtr_dbg(const char *frmt, ...) __attribute__((format(printf, 1, 2))); + +#endif diff --git a/rtrlib/lib/utils.c b/rtrlib/lib/utils.c new file mode 100644 index 0000000..ca708e9 --- /dev/null +++ b/rtrlib/lib/utils.c @@ -0,0 +1,54 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "utils_private.h" + +#include <assert.h> +#include <stdint.h> +#include <time.h> + +#ifdef __MACH__ +#include <mach/mach_time.h> +static double timeconvert = 0.0; +#endif + +int lrtr_get_monotonic_time(time_t *seconds) +{ +#ifdef __MACH__ + if (timeconvert == 0.0) { + mach_timebase_info_data_t time_base; + (void)mach_timebase_info(&time_base); + timeconvert = (double)time_base.numer / (double)time_base.denom / 1000000000.0; + } + *seconds = (time_t)mach_absolute_time() * timeconvert; +#else + struct timespec time; + + if (clock_gettime(CLOCK_MONOTONIC, &time) == -1) + return -1; + *seconds = time.tv_sec; + if ((time.tv_nsec * 1000000000) >= 5) + *seconds += 1; +#endif + return 0; +} + +uint32_t lrtr_get_bits(const uint32_t val, const uint8_t from, const uint8_t number) +{ + assert(number < 33); + assert(number > 0); + + uint32_t mask = ~0; + + if (number != 32) + mask = ~(mask >> number); + + mask >>= from; + return (mask & val); +} diff --git a/rtrlib/lib/utils_private.h b/rtrlib/lib/utils_private.h new file mode 100644 index 0000000..40e99ee --- /dev/null +++ b/rtrlib/lib/utils_private.h @@ -0,0 +1,34 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef LRTR_UTILS_PRIVATE_H +#define LRTR_UTILS_PRIVATE_H + +#include <stdint.h> +#include <time.h> + +/** + * @brief Returns the current time of the CLOCK_MONOTONIC clock. + * @param[in] seconds Time in seconds since some unspecified starting point. + * @return 0 on successs + * @return -1 on error + */ +int lrtr_get_monotonic_time(time_t *seconds); + +/** + * @brief Extracts number bits from the passed uint32_t, starting at bit number from. The bit with the highest + * significance is bit 0. All bits that aren't in the specified range will be 0. + * @param[in] val uint32_t + * @param[in] from Position of the first bit that is extracted. + * @param[in] number How many bits will be extracted. + * @returns a uint32_t, where all bits that aren't in the specified range are set to 0. + */ +uint32_t lrtr_get_bits(const uint32_t val, const uint8_t from, const uint8_t number); + +#endif diff --git a/rtrlib/pfx/pfx.h b/rtrlib/pfx/pfx.h new file mode 100644 index 0000000..712f416 --- /dev/null +++ b/rtrlib/pfx/pfx.h @@ -0,0 +1,160 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_pfx_h Prefix validation table + * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data + * received from an RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_PFX_H +#define RTR_PFX_H + +#include "rtrlib/lib/ip.h" +#include "rtrlib/pfx/trie/trie-pfx.h" + +#include <inttypes.h> + +/** + * @brief Possible return values for pfx_ functions. + */ +enum pfx_rtvals { + /** Operation was successful. */ + PFX_SUCCESS = 0, + + /** Error occurred. */ + PFX_ERROR = -1, + + /** The supplied pfx_record already exists in the pfx_table. */ + PFX_DUPLICATE_RECORD = -2, + + /** pfx_record wasn't found in the pfx_table. */ + PFX_RECORD_NOT_FOUND = -3 +}; + +/** + * @brief Validation states returned from pfx_validate_origin. + */ +enum pfxv_state { + /** A valid certificate for the pfx_record exists. */ + BGP_PFXV_STATE_VALID, + + /** @brief No certificate for the route exists. */ + BGP_PFXV_STATE_NOT_FOUND, + + /** @brief One or more records that match the input prefix exists in the pfx_table + * but the prefix max_len or ASN doesn't match. + */ + BGP_PFXV_STATE_INVALID +}; + +/** + * @brief A function pointer that is called for each record in the pfx_table. + * @param pfx_record + * @param data forwarded data which the user has passed to pfx_table_for_each_ipv4_record() or + * pfx_table_for_each_ipv6_record() + */ +typedef void (*pfx_for_each_fp)(const struct pfx_record *pfx_record, void *data); + +/** + * @brief Initializes the pfx_table struct. + * @param[in] pfx_table pfx_table that will be initialized. + * @param[in] update_fp A function pointer that will be called if a record was added or removed. + */ +void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp); + +/** + * @brief Frees all memory associated with the pfx_table. + * @param[in] pfx_table pfx_table that will be freed. + */ +void pfx_table_free(struct pfx_table *pfx_table); + +/** + * @brief Adds a pfx_record to a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] pfx_record pfx_record that will be added. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + * @return PFX_DUPLICATE_RECORD If the pfx_record already exists. + */ +int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *pfx_record); + +/** + * @brief Removes a pfx_record from a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] pfx_record Record that will be removed. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + * @return PFX_RECORD_NOT_FOUND If pfx_records couldn't be found. + */ +int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *pfx_record); + +/** + * @brief Removes all entries in the pfx_table that match the passed socket_id value from a pfx_table. + * @param[in] pfx_table pfx_table to use. + * @param[in] socket origin socket of the record + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket); + +/** + * @brief Validates the origin of a BGP-Route. + * @param[in] pfx_table pfx_table to use. + * @param[in] asn Autonomous system number of the Origin-AS of the route. + * @param[in] prefix Announced network Prefix. + * @param[in] mask_len Length of the network mask of the announced prefix. + * @param[out] result Result of the validation. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, enum pfxv_state *result); + +/** + * @brief Validates the origin of a BGP-Route and returns a list of pfx_record that decided the result. + * @param[in] pfx_table pfx_table to use. + * @param[out] reason Pointer to a memory area that will be used as array of pfx_records. + * The memory area will be overwritten. Reason must point to NULL or an allocated memory area. + * @param[out] reason_len Size of the array reason. + * @param[in] asn Autonomous system number of the Origin-AS of the route. + * @param[in] prefix Announced network Prefix + * @param[in] mask_len Length of the network mask of the announced prefix + * @param[out] result Result of the validation. + * @return PFX_SUCCESS On success. + * @return PFX_ERROR On error. + */ +int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason, unsigned int *reason_len, + const uint32_t asn, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + enum pfxv_state *result); + +/** + * @brief Iterates over all IPv4 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] pfx_table + * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data); + +/** + * @brief Iterates over all IPv6 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] pfx_table + * @param[in] fp A pointer to a callback function with the signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data); + +#endif +/** @} */ diff --git a/rtrlib/pfx/pfx_private.h b/rtrlib/pfx/pfx_private.h new file mode 100644 index 0000000..6ec1d8a --- /dev/null +++ b/rtrlib/pfx/pfx_private.h @@ -0,0 +1,60 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_pfx_h Prefix validation table + * @brief The pfx_table is an abstract data structure to organize the validated prefix origin data + * received from an RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_PFX_PRIVATE_H +#define RTR_PFX_PRIVATE_H + +#include "pfx.h" + +#include "rtrlib/lib/ip_private.h" + +#include <stdint.h> + +/** + * @brief Frees all memory associated with the pfx_table without calling the update callback. + * @param[in] pfx_table pfx_table that will be freed. + */ +void pfx_table_free_without_notify(struct pfx_table *pfx_table); + +/** + * @brief Swap root nodes of the argument tables + * @param[in,out] a First table + * @param[in,out] b second table + */ +void pfx_table_swap(struct pfx_table *a, struct pfx_table *b); + +/** + * @brief Copy content of @p src_table into @p dst_table + * @details dst must be empty and initialized + * @param[in] src_table Source table + * @param[out] dst_table Destination table + * @param[in] socket socket which prefixes should not be copied + */ +int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table, + const struct rtr_socket *socket); + +/** + * @brief Notify client about changes between to pfx tables regarding one specific socket + * @details old_table will be modified it should be freed after calling this function + * @param[in] new_table + * @param[in] old_table + * @param[in] socket socket which prefixes should be diffed + */ +void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket); + +#endif +/** @} */ diff --git a/rtrlib/pfx/trie/trie-pfx.c b/rtrlib/pfx/trie/trie-pfx.c new file mode 100644 index 0000000..ffb2416 --- /dev/null +++ b/rtrlib/pfx/trie/trie-pfx.c @@ -0,0 +1,645 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "trie-pfx.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/ip_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/pfx/trie/trie_private.h" +#include "rtrlib/rtrlib_export_private.h" + +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> + +struct data_elem { + uint32_t asn; + uint8_t max_len; + const struct rtr_socket *socket; +}; + +struct node_data { + unsigned int len; + struct data_elem *ary; +}; + +struct copy_cb_args { + struct pfx_table *pfx_table; + const struct rtr_socket *socket; + bool error; +}; + +struct notify_diff_cb_args { + struct pfx_table *old_table; + struct pfx_table *new_table; + const struct rtr_socket *socket; + pfx_update_fp pfx_update_fp; + bool added; +}; + +static struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver); +static int pfx_table_del_elem(struct node_data *data, const unsigned int index); +static int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record); +static int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record); +static struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record, + unsigned int *index); +static bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len); +static void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added); +static int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node, + const struct rtr_socket *socket, const unsigned int level); +static int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record records[], const unsigned int ary_len); +static void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len); + +void pfx_table_notify_clients(struct pfx_table *pfx_table, const struct pfx_record *record, const bool added) +{ + if (pfx_table->update_fp) + pfx_table->update_fp(pfx_table, *record, added); +} + +RTRLIB_EXPORT void pfx_table_init(struct pfx_table *pfx_table, pfx_update_fp update_fp) +{ + pfx_table->ipv4 = NULL; + pfx_table->ipv6 = NULL; + pfx_table->update_fp = update_fp; + pthread_rwlock_init(&(pfx_table->lock), NULL); +} + +void pfx_table_free_without_notify(struct pfx_table *pfx_table) +{ + pfx_table->update_fp = NULL; + pfx_table_free(pfx_table); +} + +RTRLIB_EXPORT void pfx_table_free(struct pfx_table *pfx_table) +{ + for (int i = 0; i < 2; i++) { + struct trie_node *root = (i == 0 ? pfx_table->ipv4 : pfx_table->ipv6); + + if (root) { + struct trie_node *rm_node; + + pthread_rwlock_wrlock(&(pfx_table->lock)); + do { + struct node_data *data = (struct node_data *)(root->data); + + for (unsigned int j = 0; j < data->len; j++) { + struct pfx_record record = {data->ary[j].asn, (root->prefix), root->len, + data->ary[j].max_len, data->ary[j].socket}; + pfx_table_notify_clients(pfx_table, &record, false); + } + rm_node = (trie_remove(root, &(root->prefix), root->len, 0)); + assert(rm_node); + lrtr_free(((struct node_data *)rm_node->data)->ary); + lrtr_free(rm_node->data); + lrtr_free(rm_node); + } while (rm_node != root); + if (i == 0) + pfx_table->ipv4 = NULL; + else + pfx_table->ipv6 = NULL; + + pthread_rwlock_unlock(&(pfx_table->lock)); + } + } + pthread_rwlock_destroy(&(pfx_table->lock)); +} + +int pfx_table_append_elem(struct node_data *data, const struct pfx_record *record) +{ + struct data_elem *tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * ((data->len) + 1)); + + if (!tmp) + return PFX_ERROR; + data->len++; + data->ary = tmp; + data->ary[data->len - 1].asn = record->asn; + data->ary[data->len - 1].max_len = record->max_len; + data->ary[data->len - 1].socket = record->socket; + return PFX_SUCCESS; +} + +int pfx_table_create_node(struct trie_node **node, const struct pfx_record *record) +{ + int err; + + *node = lrtr_malloc(sizeof(struct trie_node)); + if (!*node) + return PFX_ERROR; + + (*node)->prefix = record->prefix; + (*node)->len = record->min_len; + (*node)->lchild = NULL; + (*node)->rchild = NULL; + (*node)->parent = NULL; + + (*node)->data = lrtr_malloc(sizeof(struct node_data)); + if (!(*node)->data) { + err = PFX_ERROR; + goto free_node; + } + + ((struct node_data *)(*node)->data)->len = 0; + ((struct node_data *)(*node)->data)->ary = NULL; + + err = pfx_table_append_elem(((struct node_data *)(*node)->data), record); + if (err) + goto free_node_data; + + return PFX_SUCCESS; + +free_node_data: + lrtr_free((*node)->data); +free_node: + lrtr_free(*node); + + return err; +} + +struct data_elem *pfx_table_find_elem(const struct node_data *data, const struct pfx_record *record, + unsigned int *index) +{ + for (unsigned int i = 0; i < data->len; i++) { + if (data->ary[i].asn == record->asn && data->ary[i].max_len == record->max_len && + data->ary[i].socket == record->socket) { + if (index) + *index = i; + return &(data->ary[i]); + } + } + return NULL; +} + +// returns pfx_table->ipv4 if record version is LRTR_IPV4 else pfx_table->ipv6 +inline struct trie_node *pfx_table_get_root(const struct pfx_table *pfx_table, const enum lrtr_ip_version ver) +{ + return ver == LRTR_IPV4 ? pfx_table->ipv4 : pfx_table->ipv6; +} + +int pfx_table_del_elem(struct node_data *data, const unsigned int index) +{ + struct data_elem *tmp; + struct data_elem deleted_elem = data->ary[index]; + + // if index is not the last elem in the list, move all other elems backwards in the array + if (index != data->len - 1) { + for (unsigned int i = index; i < data->len - 1; i++) + data->ary[i] = data->ary[i + 1]; + } + + data->len--; + if (!data->len) { + lrtr_free(data->ary); + data->ary = NULL; + return PFX_SUCCESS; + } + + tmp = lrtr_realloc(data->ary, sizeof(struct data_elem) * data->len); + if (!tmp) { + data->ary[data->len] = deleted_elem; + data->len++; + return PFX_ERROR; + } + + data->ary = tmp; + + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_add(struct pfx_table *pfx_table, const struct pfx_record *record) +{ + pthread_rwlock_wrlock(&(pfx_table->lock)); + + struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver); + unsigned int lvl = 0; + + if (root) { + bool found; + struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found); + + if (found) { // node with prefix exists + if (pfx_table_find_elem(node->data, record, NULL)) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_DUPLICATE_RECORD; + } + // append record to note_data array + int rtval = pfx_table_append_elem(node->data, record); + + pthread_rwlock_unlock(&pfx_table->lock); + if (rtval == PFX_SUCCESS) + pfx_table_notify_clients(pfx_table, record, true); + return rtval; + } + + // no node with same prefix and prefix_len found + struct trie_node *new_node = NULL; + + if (pfx_table_create_node(&new_node, record) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + trie_insert(node, new_node, lvl); + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_notify_clients(pfx_table, record, true); + return PFX_SUCCESS; + } + + // tree is empty, record will be the root_node + struct trie_node *new_node = NULL; + + if (pfx_table_create_node(&new_node, record) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + if (record->prefix.ver == LRTR_IPV4) + pfx_table->ipv4 = new_node; + else + pfx_table->ipv6 = new_node; + + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_notify_clients(pfx_table, record, true); + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_remove(struct pfx_table *pfx_table, const struct pfx_record *record) +{ + pthread_rwlock_wrlock(&(pfx_table->lock)); + struct trie_node *root = pfx_table_get_root(pfx_table, record->prefix.ver); + + unsigned int lvl = 0; // tree depth were node was found + bool found; + struct trie_node *node = trie_lookup_exact(root, &(record->prefix), record->min_len, &lvl, &found); + + if (!found) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_RECORD_NOT_FOUND; + } + + unsigned int index; + struct data_elem *elem = pfx_table_find_elem(node->data, record, &index); + + if (!elem) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_RECORD_NOT_FOUND; + } + + struct node_data *ndata = (struct node_data *)node->data; + + if (pfx_table_del_elem(ndata, index) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + + if (ndata->len == 0) { + node = trie_remove(node, &(record->prefix), record->min_len, lvl); + assert(node); + + if (node == root) { + if (record->prefix.ver == LRTR_IPV4) + pfx_table->ipv4 = NULL; + else + pfx_table->ipv6 = NULL; + } + assert(((struct node_data *)node->data)->len == 0); + lrtr_free(node->data); + lrtr_free(node); + } + pthread_rwlock_unlock(&pfx_table->lock); + + pfx_table_notify_clients(pfx_table, record, false); + + return PFX_SUCCESS; +} + +bool pfx_table_elem_matches(struct node_data *data, const uint32_t asn, const uint8_t prefix_len) +{ + for (unsigned int i = 0; i < data->len; i++) { + if (data->ary[i].asn != 0 && data->ary[i].asn == asn && prefix_len <= data->ary[i].max_len) + return true; + } + return false; +} + +int pfx_table_node2pfx_record(struct trie_node *node, struct pfx_record *records, const unsigned int ary_len) +{ + struct node_data *data = node->data; + + if (ary_len < data->len) + return PFX_ERROR; + + for (unsigned int i = 0; i < data->len; i++) { + records[i].asn = data->ary[i].asn; + records[i].prefix = node->prefix; + records[i].min_len = node->len; + records[i].max_len = data->ary[i].max_len; + records[i].socket = data->ary[i].socket; + } + return data->len; +} + +inline void pfx_table_free_reason(struct pfx_record **reason, unsigned int *reason_len) +{ + if (reason) { + lrtr_free(*reason); + *reason = NULL; + } + if (reason_len) + *reason_len = 0; +} + +RTRLIB_EXPORT int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_record **reason, + unsigned int *reason_len, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t prefix_len, enum pfxv_state *result) +{ + // assert(reason_len == NULL || *reason_len == 0); + // assert(reason == NULL || *reason == NULL); + + pthread_rwlock_rdlock(&(pfx_table->lock)); + struct trie_node *root = pfx_table_get_root(pfx_table, prefix->ver); + + if (!root) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_NOT_FOUND; + pfx_table_free_reason(reason, reason_len); + return PFX_SUCCESS; + } + + unsigned int lvl = 0; + struct trie_node *node = trie_lookup(root, prefix, prefix_len, &lvl); + + if (!node) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_NOT_FOUND; + pfx_table_free_reason(reason, reason_len); + return PFX_SUCCESS; + } + + if (reason_len && reason) { + *reason_len = ((struct node_data *)node->data)->len; + *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record)); + if (!*reason) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + if (pfx_table_node2pfx_record(node, *reason, *reason_len) == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + } + + while (!pfx_table_elem_matches(node->data, asn, prefix_len)) { + if (lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits( + prefix, lvl++, + 1))) //post-incr lvl, trie_lookup is performed on child_nodes => parent lvl + 1 + node = trie_lookup(node->lchild, prefix, prefix_len, &lvl); + else + node = trie_lookup(node->rchild, prefix, prefix_len, &lvl); + + if (!node) { + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_INVALID; + return PFX_SUCCESS; + } + + if (reason_len && reason) { + unsigned int r_len_old = *reason_len; + *reason_len += ((struct node_data *)node->data)->len; + *reason = lrtr_realloc(*reason, *reason_len * sizeof(struct pfx_record)); + struct pfx_record *start = *reason + r_len_old; + + if (!*reason) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + if (pfx_table_node2pfx_record(node, start, ((struct node_data *)node->data)->len) == + PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + pfx_table_free_reason(reason, reason_len); + return PFX_ERROR; + } + } + } + + pthread_rwlock_unlock(&pfx_table->lock); + *result = BGP_PFXV_STATE_VALID; + return PFX_SUCCESS; +} + +RTRLIB_EXPORT int pfx_table_validate(struct pfx_table *pfx_table, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t prefix_len, enum pfxv_state *result) +{ + return pfx_table_validate_r(pfx_table, NULL, NULL, asn, prefix, prefix_len, result); +} + +RTRLIB_EXPORT int pfx_table_src_remove(struct pfx_table *pfx_table, const struct rtr_socket *socket) +{ + for (unsigned int i = 0; i < 2; i++) { + struct trie_node **root = (i == 0 ? &(pfx_table->ipv4) : &(pfx_table->ipv6)); + + pthread_rwlock_wrlock(&(pfx_table->lock)); + if (*root) { + int rtval = pfx_table_remove_id(pfx_table, root, *root, socket, 0); + + if (rtval == PFX_ERROR) { + pthread_rwlock_unlock(&pfx_table->lock); + return PFX_ERROR; + } + } + pthread_rwlock_unlock(&pfx_table->lock); + } + return PFX_SUCCESS; +} + +int pfx_table_remove_id(struct pfx_table *pfx_table, struct trie_node **root, struct trie_node *node, + const struct rtr_socket *socket, const unsigned int level) +{ + assert(node); + assert(root); + assert(*root); + bool check_node = true; + + while (check_node) { + // data from removed node are replaced from data from child nodes (if children exists), + // same node must be checked again if it was replaced with previous child node data + struct node_data *data = node->data; + + for (unsigned int i = 0; i < data->len; i++) { + while (data->len > i && data->ary[i].socket == socket) { + struct pfx_record record = {data->ary[i].asn, node->prefix, node->len, + data->ary[i].max_len, data->ary[i].socket}; + if (pfx_table_del_elem(data, i) == PFX_ERROR) + return PFX_ERROR; + pfx_table_notify_clients(pfx_table, &record, false); + } + } + if (data->len == 0) { + struct trie_node *rm_node = trie_remove(node, &(node->prefix), node->len, level); + + assert(rm_node); + assert(((struct node_data *)rm_node->data)->len == 0); + lrtr_free(((struct node_data *)rm_node->data)); + lrtr_free(rm_node); + + if (rm_node == *root) { + *root = NULL; + return PFX_SUCCESS; + } else if (rm_node == node) { + return PFX_SUCCESS; + } + } else { + check_node = false; + } + } + + if (node->lchild) { + if (pfx_table_remove_id(pfx_table, root, node->lchild, socket, level + 1) == PFX_ERROR) + return PFX_ERROR; + } + if (node->rchild) + return pfx_table_remove_id(pfx_table, root, node->rchild, socket, level + 1); + return PFX_SUCCESS; +} + +static void pfx_table_for_each_rec(struct trie_node *n, pfx_for_each_fp fp, void *data) +{ + struct pfx_record pfxr; + struct node_data *nd; + + assert(n); + assert(fp); + + nd = (struct node_data *)n->data; + assert(nd); + + if (n->lchild) + pfx_table_for_each_rec(n->lchild, fp, data); + + for (unsigned int i = 0; i < nd->len; i++) { + pfxr.asn = nd->ary[i].asn; + pfxr.prefix = n->prefix; + pfxr.min_len = n->len; + pfxr.max_len = nd->ary[i].max_len; + pfxr.socket = nd->ary[i].socket; + fp(&pfxr, data); + } + + if (n->rchild) + pfx_table_for_each_rec(n->rchild, fp, data); +} + +RTRLIB_EXPORT void pfx_table_for_each_ipv4_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data) +{ + assert(pfx_table); + + if (!pfx_table->ipv4) + return; + + pthread_rwlock_rdlock(&(pfx_table->lock)); + pfx_table_for_each_rec(pfx_table->ipv4, fp, data); + pthread_rwlock_unlock(&pfx_table->lock); +} + +RTRLIB_EXPORT void pfx_table_for_each_ipv6_record(struct pfx_table *pfx_table, pfx_for_each_fp fp, void *data) +{ + assert(pfx_table); + + if (!pfx_table->ipv6) + return; + + pthread_rwlock_rdlock(&(pfx_table->lock)); + pfx_table_for_each_rec(pfx_table->ipv6, fp, data); + pthread_rwlock_unlock(&pfx_table->lock); +} + +static void pfx_table_copy_cb(const struct pfx_record *record, void *data) +{ + struct copy_cb_args *args = data; + + if (record->socket != args->socket) { + if (pfx_table_add(args->pfx_table, record) != PFX_SUCCESS) + args->error = true; + } +} + +int pfx_table_copy_except_socket(struct pfx_table *src_table, struct pfx_table *dst_table, + const struct rtr_socket *socket) +{ + struct copy_cb_args args = {dst_table, socket, false}; + + pfx_table_for_each_ipv4_record(src_table, pfx_table_copy_cb, &args); + if (args.error) + return PFX_ERROR; + + pfx_table_for_each_ipv6_record(src_table, pfx_table_copy_cb, &args); + if (args.error) + return PFX_ERROR; + + return PFX_SUCCESS; +} + +void pfx_table_swap(struct pfx_table *a, struct pfx_table *b) +{ + struct trie_node *ipv4_tmp; + struct trie_node *ipv6_tmp; + + pthread_rwlock_wrlock(&(a->lock)); + pthread_rwlock_wrlock(&(b->lock)); + + ipv4_tmp = a->ipv4; + ipv6_tmp = a->ipv6; + + a->ipv4 = b->ipv4; + a->ipv6 = b->ipv6; + + b->ipv4 = ipv4_tmp; + b->ipv6 = ipv6_tmp; + + pthread_rwlock_unlock(&(b->lock)); + pthread_rwlock_unlock(&(a->lock)); +} + +static void pfx_table_notify_diff_cb(const struct pfx_record *record, void *data) +{ + struct notify_diff_cb_args *args = data; + + if (args->socket == record->socket && args->added) { + if (pfx_table_remove(args->old_table, record) != PFX_SUCCESS) + pfx_table_notify_clients(args->new_table, record, args->added); + } else if (args->socket == record->socket && !args->added) { + pfx_table_notify_clients(args->new_table, record, args->added); + } +} + +void pfx_table_notify_diff(struct pfx_table *new_table, struct pfx_table *old_table, const struct rtr_socket *socket) +{ + pfx_update_fp old_table_fp; + struct notify_diff_cb_args args = {old_table, new_table, socket, new_table->update_fp, true}; + + // Disable update callback for old_table table + old_table_fp = old_table->update_fp; + old_table->update_fp = NULL; + + // Iterate new_table and try to delete every prefix from the given socket in old_table + // If the prefix could not be removed it was added in new_table and the update cb must be called + pfx_table_for_each_ipv4_record(new_table, pfx_table_notify_diff_cb, &args); + pfx_table_for_each_ipv6_record(new_table, pfx_table_notify_diff_cb, &args); + + // Iterate over old_table table and search and remove remaining prefixes from the socket + // issue a remove notification for every one of them, because they are not present in new_table. + args.added = false; + pfx_table_for_each_ipv4_record(old_table, pfx_table_notify_diff_cb, &args); + pfx_table_for_each_ipv6_record(old_table, pfx_table_notify_diff_cb, &args); + + // Restore original state of old_tables update_fp + old_table->update_fp = old_table_fp; +} diff --git a/rtrlib/pfx/trie/trie-pfx.h b/rtrlib/pfx/trie/trie-pfx.h new file mode 100644 index 0000000..7a8f69a --- /dev/null +++ b/rtrlib/pfx/trie/trie-pfx.h @@ -0,0 +1,73 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_trie_pfx_h Trie + * @ingroup mod_pfx_h + * @brief An implementation of a \ref mod_pfx_h "pfx_table" data structure + * using a shortest prefix first tree (trie) for storing @ref pfx_record "pfx_records". + * @details This implementation uses two separate lpfs-trees, one for IPv4 + * validation records and one for IPv6 records.\n + * See \ref mod_pfx_h "pfx_table" for a list of supported operations of + * this data structure.\n + * + * @{ + */ + +#ifndef RTR_TRIE_PFX +#define RTR_TRIE_PFX + +#include "rtrlib/lib/ip.h" + +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +struct pfx_table; + +/** + * @brief pfx_record. + * @param asn Origin AS number. + * @param prefix IP prefix. + * @param min_len Minimum prefix length. + * @param max_len Maximum prefix length. + * @param socket The rtr_socket that received this record. + */ +struct pfx_record { + uint32_t asn; + struct lrtr_ip_addr prefix; + uint8_t min_len; + uint8_t max_len; + const struct rtr_socket *socket; +}; + +/** + * @brief A function pointer that is called if an record was added to the pfx_table or was removed from the pfx_table. + * @param pfx_table which was updated. + * @param record pfx_record that was modified. + * @param added True if the record was added, false if the record was removed. + */ +typedef void (*pfx_update_fp)(struct pfx_table *pfx_table, const struct pfx_record record, const bool added); + +/** + * @brief pfx_table. + * @param ipv4 + * @param ipv6 + * @param update_fp + * @param lock + */ +struct pfx_table { + struct trie_node *ipv4; + struct trie_node *ipv6; + pfx_update_fp update_fp; + pthread_rwlock_t lock; +}; + +#endif +/** @} */ diff --git a/rtrlib/pfx/trie/trie.c b/rtrlib/pfx/trie/trie.c new file mode 100644 index 0000000..b359892 --- /dev/null +++ b/rtrlib/pfx/trie/trie.c @@ -0,0 +1,235 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "trie_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/ip_private.h" + +#include <assert.h> +#include <stdlib.h> + +static void swap_nodes(struct trie_node *a, struct trie_node *b) +{ + struct trie_node tmp; + + tmp.prefix = a->prefix; + tmp.len = a->len; + tmp.data = a->data; + + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; + + b->prefix = tmp.prefix; + b->len = tmp.len; + b->data = tmp.data; +} + +enum child_node_rel { LEFT, RIGHT }; + +static void add_child_node(struct trie_node *parent, struct trie_node *child, enum child_node_rel rel) +{ + assert(rel == LEFT || rel == RIGHT); + + if (rel == LEFT) + parent->lchild = child; + else + parent->rchild = child; + + child->parent = parent; +} + +static inline bool is_left_child(const struct lrtr_ip_addr *addr, unsigned int lvl) +{ + /* A node must be inserted as left child if bit <lvl> of the IP address + * is 0 otherwise as right child + */ + return lrtr_ip_addr_is_zero(lrtr_ip_addr_get_bits(addr, lvl, 1)); +} + +void trie_insert(struct trie_node *root, struct trie_node *new, const unsigned int lvl) +{ + if (new->len < root->len) + swap_nodes(root, new); + + if (is_left_child(&new->prefix, lvl)) { + if (!root->lchild) + return add_child_node(root, new, LEFT); + + return trie_insert(root->lchild, new, lvl + 1); + } + + if (!root->rchild) + return add_child_node(root, new, RIGHT); + + trie_insert(root->rchild, new, lvl + 1); +} + +struct trie_node *trie_lookup(const struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + unsigned int *lvl) +{ + while (root) { + if (root->len <= mask_len && lrtr_ip_addr_equal(lrtr_ip_addr_get_bits(&root->prefix, 0, root->len), + lrtr_ip_addr_get_bits(prefix, 0, root->len))) + return (struct trie_node *)root; + + if (is_left_child(prefix, *lvl)) + root = root->lchild; + else + root = root->rchild; + + (*lvl)++; + } + return NULL; +} + +struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *lvl, bool *found) +{ + *found = false; + + while (root_node) { + if (*lvl > 0 && root_node->len > mask_len) { + (*lvl)--; + return root_node->parent; + } + + if (root_node->len == mask_len && lrtr_ip_addr_equal(root_node->prefix, *prefix)) { + *found = true; + return root_node; + } + + if (is_left_child(prefix, *lvl)) { + if (!root_node->lchild) + return root_node; + root_node = root_node->lchild; + } else { + if (!root_node->rchild) + return root_node; + root_node = root_node->rchild; + } + + (*lvl)++; + } + return NULL; +} + +static void deref_node(struct trie_node *n) +{ + if (!n->parent) + return; + + if (n->parent->lchild == n) { + n->parent->lchild = NULL; + return; + } + + n->parent->rchild = NULL; +} + +static inline bool prefix_is_same(const struct trie_node *n, const struct lrtr_ip_addr *p, uint8_t mask_len) +{ + return n->len == mask_len && lrtr_ip_addr_equal(n->prefix, *p); +} + +static void replace_node_data(struct trie_node *a, struct trie_node *b) +{ + a->prefix = b->prefix; + a->len = b->len; + a->data = b->data; +} + +struct trie_node *trie_remove(struct trie_node *root, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + const unsigned int lvl) +{ + /* If the node has no children we can simply remove it + * If the node has children, we swap the node with the child that + * has the smaller prefix length and drop the child. + */ + if (prefix_is_same(root, prefix, mask_len)) { + void *tmp; + + if (trie_is_leaf(root)) { + deref_node(root); + return root; + } + + /* swap with the left child and drop the child */ + if (root->lchild && (!root->rchild || root->lchild->len < root->rchild->len)) { + tmp = root->data; + replace_node_data(root, root->lchild); + root->lchild->data = tmp; + + return trie_remove(root->lchild, &root->lchild->prefix, root->lchild->len, lvl + 1); + } + + /* swap with the right child and drop the child */ + tmp = root->data; + replace_node_data(root, root->rchild); + root->rchild->data = tmp; + + return trie_remove(root->rchild, &root->rchild->prefix, root->rchild->len, lvl + 1); + } + + if (is_left_child(prefix, lvl)) { + if (!root->lchild) + return NULL; + return trie_remove(root->lchild, prefix, mask_len, lvl + 1); + } + + if (!root->rchild) + return NULL; + return trie_remove(root->rchild, prefix, mask_len, lvl + 1); +} + +static int append_node_to_array(struct trie_node ***ary, unsigned int *len, struct trie_node *n) +{ + struct trie_node **new; + + new = lrtr_realloc(*ary, *len * sizeof(n)); + if (!new) + return -1; + + *ary = new; + (*ary)[*len - 1] = n; + return 0; +} + +int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len) +{ + if (root_node->lchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->lchild)) + goto err; + + if (trie_get_children(root_node->lchild, array, len) == -1) + goto err; + } + + if (root_node->rchild) { + *len += 1; + if (append_node_to_array(array, len, root_node->rchild)) + goto err; + + if (trie_get_children(root_node->rchild, array, len) == -1) + goto err; + } + + return 0; + +err: + lrtr_free(*array); + return -1; +} + +inline bool trie_is_leaf(const struct trie_node *node) +{ + return !node->lchild && !node->rchild; +} diff --git a/rtrlib/pfx/trie/trie_private.h b/rtrlib/pfx/trie/trie_private.h new file mode 100644 index 0000000..8003ced --- /dev/null +++ b/rtrlib/pfx/trie/trie_private.h @@ -0,0 +1,98 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTR_TRIE_PRIVATE +#define RTR_TRIE_PRIVATE + +#include "rtrlib/lib/ip_private.h" + +#include <inttypes.h> + +/** + * @brief trie_node + * @param prefix + * @param rchild + * @param lchild + * @param parent + * @param data + * @param len number of elements in data array + */ +struct trie_node { + struct lrtr_ip_addr prefix; + struct trie_node *rchild; + struct trie_node *lchild; + struct trie_node *parent; + void *data; + uint8_t len; +}; + +/** + * @brief Inserts new_node in the tree. + * @param[in] root Root node of the tree for the inserting process. + * @param[in] new_node Node that will be inserted. + * @param[in] level Level of the the root node in the tree. + */ +void trie_insert(struct trie_node *root, struct trie_node *new_node, const unsigned int level); + +/** + * @brief Searches for a matching node matching the passed ip + * prefix and prefix length. If multiple matching nodes exist, the one + * with the shortest prefix is returned. + * @param[in] root_node Node were the lookup process starts. + * @param[in] lrtr_ip_addr IP-Prefix. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in,out] level of the the node root in the tree. Is set to the level of + * the node that is returned. + * @returns The trie_node with the short prefix in the tree matching the + * passed ip prefix and prefix length. + * @returns NULL if no node that matches the passed prefix and prefix length + * could be found. + */ +struct trie_node *trie_lookup(const struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *level); + +/** + * @brief Search for a node with the same prefix and prefix length. + * @param[in] root_node Node were the lookup process starts. + * @param[in] lrtr_ip_addr IP-Prefix. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in,out] level of the the node root in the tree. Is set to the level of + * the node that is returned. + * @param[in] found Is true if a node which matches could be found else found is + * set to false. + * @return A node which matches the passed parameter (found==true). + * @return The parent of the node where the lookup operation + * stopped (found==false). + * @return NULL if root_node is NULL. + */ +struct trie_node *trie_lookup_exact(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, unsigned int *level, bool *found); + +/** + * @brief Removes the node with the passed IP prefix and mask_len from the tree. + * @param[in] root Node were the inserting process starts. + * @param[in] prefix Prefix that will removed from the tree. + * @param[in] mask_len Length of the network mask of the prefix. + * @param[in] level Level of the root node in the tree. + * @returns Node that was removed from the tree. The caller has to free it. + * @returns NULL If the Prefix couldn't be found in the tree. + */ +struct trie_node *trie_remove(struct trie_node *root_node, const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + const unsigned int level); + +/** + * @brief Detects if a node is a leaf in the tree. + * @param[in] node + * @returns true if node is a leaf. + * @returns false if node isn't a leaf. + */ +bool trie_is_leaf(const struct trie_node *node); + +int trie_get_children(const struct trie_node *root_node, struct trie_node ***array, unsigned int *len); +#endif diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c new file mode 100644 index 0000000..ec00779 --- /dev/null +++ b/rtrlib/rtr/packets.c @@ -0,0 +1,1517 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "packets_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/convert_byte_order_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/rtr/rtr_private.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MGR_DBG1(a) lrtr_dbg("RTR_MGR: " a) +#define TEMPORARY_PDU_STORE_INCREMENT_VALUE 100 +#define MAX_SUPPORTED_PDU_TYPE 10 + +enum pdu_error_type { + CORRUPT_DATA = 0, + INTERNAL_ERROR = 1, + NO_DATA_AVAIL = 2, + INVALID_REQUEST = 3, + UNSUPPORTED_PROTOCOL_VER = 4, + UNSUPPORTED_PDU_TYPE = 5, + WITHDRAWAL_OF_UNKNOWN_RECORD = 6, + DUPLICATE_ANNOUNCEMENT = 7, + UNEXPECTED_PROTOCOL_VERSION = 8, + PDU_TOO_BIG = 32 +}; + +enum pdu_type { + SERIAL_NOTIFY = 0, + SERIAL_QUERY = 1, + RESET_QUERY = 2, + CACHE_RESPONSE = 3, + IPV4_PREFIX = 4, + RESERVED = 5, + IPV6_PREFIX = 6, + EOD = 7, + CACHE_RESET = 8, + ROUTER_KEY = 9, + ERROR = 10 +}; + +struct pdu_header { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; +}; + +struct pdu_cache_response { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; +}; + +struct pdu_serial_notify { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_serial_query { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_ipv4 { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; + uint8_t flags; + uint8_t prefix_len; + uint8_t max_prefix_len; + uint8_t zero; + uint32_t prefix; + uint32_t asn; +}; + +struct pdu_ipv6 { + uint8_t ver; + uint8_t type; + uint16_t reserved; + uint32_t len; + uint8_t flags; + uint8_t prefix_len; + uint8_t max_prefix_len; + uint8_t zero; + uint32_t prefix[4]; + uint32_t asn; +}; + +struct pdu_error { + uint8_t ver; + uint8_t type; + uint16_t error_code; + uint32_t len; + uint32_t len_enc_pdu; + uint8_t rest[]; +}; + +struct pdu_router_key { + uint8_t ver; + uint8_t type; + uint8_t flags; + uint8_t zero; + uint32_t len; + uint8_t ski[SKI_SIZE]; + uint32_t asn; + uint8_t spki[SPKI_SIZE]; +} __attribute__((packed)); + +/* + * 0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | + * | Version | Type | reserved = zero | + * | 0 | 2 | | + * +-------------------------------------------+ + * | | + * | Length=8 | + * | | + * `-------------------------------------------' + */ +struct pdu_reset_query { + uint8_t ver; + uint8_t type; + uint16_t flags; + uint32_t len; +}; + +struct pdu_end_of_data_v0 { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; +}; + +struct pdu_end_of_data_v1 { + uint8_t ver; + uint8_t type; + uint16_t session_id; + uint32_t len; + uint32_t sn; + uint32_t refresh_interval; + uint32_t retry_interval; + uint32_t expire_interval; +}; + +struct recv_loop_cleanup_args { + struct pdu_ipv4 *ipv4_pdus; + struct pdu_ipv6 *ipv6_pdus; + struct pdu_router_key *router_key_pdus; +}; + +static void recv_loop_cleanup(void *p); + +static int rtr_send_error_pdu_from_network(const struct rtr_socket *rtr_socket, const void *erroneous_pdu, + const uint32_t erroneous_pdu_len, const enum pdu_error_type error, + const char *err_text, const uint32_t err_text_len); + +static int rtr_send_error_pdu_from_host(const struct rtr_socket *rtr_socket, const void *erroneous_pdu, + const uint32_t erroneous_pdu_len, const enum pdu_error_type error, + const char *err_text, const uint32_t err_text_len); + +static int interval_send_error_pdu(struct rtr_socket *rtr_socket, void *pdu, uint32_t interval, uint16_t minimum, + uint32_t maximum); + +static inline enum pdu_type rtr_get_pdu_type(const void *pdu) +{ + return *((char *)pdu + 1); +} + +static int rtr_set_last_update(struct rtr_socket *rtr_socket) +{ + if (lrtr_get_monotonic_time(&(rtr_socket->last_update)) == -1) { + RTR_DBG1("get_monotonic_time(..) failed "); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + return RTR_SUCCESS; +} + +int rtr_check_interval_range(uint32_t interval, uint32_t minimum, uint32_t maximum) +{ + if (interval < minimum) + return RTR_BELOW_INTERVAL_RANGE; + else if (interval > maximum) + return RTR_ABOVE_INTERVAL_RANGE; + + return RTR_INSIDE_INTERVAL_RANGE; +} + +void apply_interval_value(struct rtr_socket *rtr_socket, uint32_t interval, enum rtr_interval_type type) +{ + if (type == RTR_INTERVAL_TYPE_EXPIRATION) + rtr_socket->expire_interval = interval; + else if (type == RTR_INTERVAL_TYPE_REFRESH) + rtr_socket->refresh_interval = interval; + else if (type == RTR_INTERVAL_TYPE_RETRY) + rtr_socket->retry_interval = interval; +} + +int rtr_check_interval_option(struct rtr_socket *rtr_socket, int interval_mode, uint32_t interval, + enum rtr_interval_type type) +{ + uint16_t minimum; + uint32_t maximum; + + int interv_retval; + + switch (type) { + case RTR_INTERVAL_TYPE_EXPIRATION: + minimum = RTR_EXPIRATION_MIN; + maximum = RTR_EXPIRATION_MAX; + interv_retval = rtr_check_interval_range(interval, minimum, maximum); + break; + case RTR_INTERVAL_TYPE_REFRESH: + minimum = RTR_REFRESH_MIN; + maximum = RTR_REFRESH_MAX; + interv_retval = rtr_check_interval_range(interval, minimum, maximum); + break; + case RTR_INTERVAL_TYPE_RETRY: + minimum = RTR_RETRY_MIN; + maximum = RTR_RETRY_MAX; + interv_retval = rtr_check_interval_range(interval, minimum, maximum); + break; + default: + RTR_DBG("Invalid interval type: %u.", type); + return RTR_ERROR; + } + + if (interv_retval == RTR_INSIDE_INTERVAL_RANGE || interval_mode == RTR_INTERVAL_MODE_ACCEPT_ANY) { + apply_interval_value(rtr_socket, interval, type); + } else if (interval_mode == RTR_INTERVAL_MODE_DEFAULT_MIN_MAX) { + if (interv_retval == RTR_BELOW_INTERVAL_RANGE) + apply_interval_value(rtr_socket, minimum, type); + else + apply_interval_value(rtr_socket, maximum, type); + } else { + RTR_DBG("Received expiration value out of range. Was %u. It will be ignored.", + interval); + } + + return RTR_SUCCESS; +} + +void rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state) +{ + if (rtr_socket->state == new_state) + return; + + // RTR_SHUTDOWN state is final,struct rtr_socket will be shutdowned can't be switched to any other state + if (rtr_socket->state == RTR_SHUTDOWN) + return; + + rtr_socket->state = new_state; + if (new_state == RTR_SHUTDOWN) + MGR_DBG1("Calling rtr_mgr_cb with RTR_SHUTDOWN"); + + if (rtr_socket->connection_state_fp) + rtr_socket->connection_state_fp(rtr_socket, new_state, rtr_socket->connection_state_fp_param_config, + rtr_socket->connection_state_fp_param_group); +} + +static void rtr_pdu_convert_header_byte_order(void *pdu, const enum target_byte_order target_byte_order) +{ + struct pdu_header *header = pdu; + + // The ROUTER_KEY PDU has two 1 Byte fields instead of the 2 Byte reserved field. + if (header->type != ROUTER_KEY) + header->reserved = lrtr_convert_short(target_byte_order, header->reserved); + + header->len = lrtr_convert_long(target_byte_order, header->len); +} + +static void rtr_pdu_convert_footer_byte_order(void *pdu, const enum target_byte_order target_byte_order) +{ + struct pdu_error *err_pdu; + struct pdu_header *header = pdu; + uint32_t addr6[4]; + const enum pdu_type type = rtr_get_pdu_type(pdu); + + switch (type) { + case SERIAL_QUERY: + ((struct pdu_serial_query *)pdu)->sn = + lrtr_convert_long(target_byte_order, ((struct pdu_serial_query *)pdu)->sn); + break; + + case ERROR: + err_pdu = (struct pdu_error *)pdu; + if (target_byte_order == TO_NETWORK_BYTE_ORDER) { + *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)) = lrtr_convert_long( + target_byte_order, *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu))); + err_pdu->len_enc_pdu = lrtr_convert_long(target_byte_order, err_pdu->len_enc_pdu); + } else { + err_pdu->len_enc_pdu = lrtr_convert_long(target_byte_order, err_pdu->len_enc_pdu); + *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu)) = lrtr_convert_long( + target_byte_order, *((uint32_t *)(err_pdu->rest + err_pdu->len_enc_pdu))); + } + break; + + case SERIAL_NOTIFY: + ((struct pdu_serial_notify *)pdu)->sn = + lrtr_convert_long(target_byte_order, ((struct pdu_serial_notify *)pdu)->sn); + break; + + case EOD: + if (header->ver == RTR_PROTOCOL_VERSION_1) { + ((struct pdu_end_of_data_v1 *)pdu)->expire_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->expire_interval); + + ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval); + + ((struct pdu_end_of_data_v1 *)pdu)->retry_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->retry_interval); + + ((struct pdu_end_of_data_v1 *)pdu)->sn = + lrtr_convert_long(target_byte_order, ((struct pdu_end_of_data_v1 *)pdu)->sn); + } else { + ((struct pdu_end_of_data_v0 *)pdu)->sn = + lrtr_convert_long(target_byte_order, ((struct pdu_end_of_data_v0 *)pdu)->sn); + } + break; + + case IPV4_PREFIX: + lrtr_ipv4_addr_convert_byte_order(((struct pdu_ipv4 *)pdu)->prefix, &((struct pdu_ipv4 *)pdu)->prefix, + target_byte_order); + ((struct pdu_ipv4 *)pdu)->asn = lrtr_convert_long(target_byte_order, ((struct pdu_ipv4 *)pdu)->asn); + break; + + case IPV6_PREFIX: + lrtr_ipv6_addr_convert_byte_order(((struct pdu_ipv6 *)pdu)->prefix, addr6, target_byte_order); + memcpy(((struct pdu_ipv6 *)pdu)->prefix, addr6, sizeof(addr6)); + ((struct pdu_ipv6 *)pdu)->asn = lrtr_convert_long(target_byte_order, ((struct pdu_ipv6 *)pdu)->asn); + break; + + case ROUTER_KEY: + ((struct pdu_router_key *)pdu)->asn = + lrtr_convert_long(target_byte_order, ((struct pdu_router_key *)pdu)->asn); + break; + + default: + break; + } +} + +static void rtr_pdu_header_to_network_byte_order(void *pdu) +{ + rtr_pdu_convert_header_byte_order(pdu, TO_NETWORK_BYTE_ORDER); +} + +static void rtr_pdu_footer_to_network_byte_order(void *pdu) +{ + rtr_pdu_convert_footer_byte_order(pdu, TO_NETWORK_BYTE_ORDER); +} + +static void rtr_pdu_to_network_byte_order(void *pdu) +{ + rtr_pdu_footer_to_network_byte_order(pdu); + rtr_pdu_header_to_network_byte_order(pdu); +} + +static void rtr_pdu_footer_to_host_byte_order(void *pdu) +{ + rtr_pdu_convert_footer_byte_order(pdu, TO_HOST_HOST_BYTE_ORDER); +} + +static void rtr_pdu_header_to_host_byte_order(void *pdu) +{ + rtr_pdu_convert_header_byte_order(pdu, TO_HOST_HOST_BYTE_ORDER); +} + +/* + * Check if the PDU is big enough for the PDU type it + * pretend to be. + * @param pdu A pointer to a PDU that is at least pdu->len byte large. + * @return False if the check fails, else true + */ +static bool rtr_pdu_check_size(const struct pdu_header *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + const struct pdu_error *err_pdu = NULL; + bool retval = false; + uint64_t min_size = 0; + + switch (type) { + case SERIAL_NOTIFY: + if (sizeof(struct pdu_serial_notify) == pdu->len) + retval = true; + break; + case CACHE_RESPONSE: + if (sizeof(struct pdu_cache_response) == pdu->len) + retval = true; + break; + case IPV4_PREFIX: + if (sizeof(struct pdu_ipv4) == pdu->len) + retval = true; + break; + case IPV6_PREFIX: + if (sizeof(struct pdu_ipv6) == pdu->len) + retval = true; + break; + case EOD: + if ((pdu->ver == RTR_PROTOCOL_VERSION_0 && (sizeof(struct pdu_end_of_data_v0) == pdu->len)) || + (pdu->ver == RTR_PROTOCOL_VERSION_1 && (sizeof(struct pdu_end_of_data_v1) == pdu->len))) { + retval = true; + } + break; + case CACHE_RESET: + if (sizeof(struct pdu_header) == pdu->len) + retval = true; + break; + case ROUTER_KEY: + if (sizeof(struct pdu_router_key) == pdu->len) + retval = true; + break; + case ERROR: + err_pdu = (const struct pdu_error *)pdu; + // +4 because of the "Length of Error Text" field + min_size = 4 + sizeof(struct pdu_error); + if (err_pdu->len < min_size) { + RTR_DBG1("PDU is too small to contain \"Length of Error Text\" field!"); + break; + } + + // Check if the PDU really contains the error PDU + uint32_t enc_pdu_len = ntohl(err_pdu->len_enc_pdu); + + RTR_DBG("enc_pdu_len: %u", enc_pdu_len); + min_size += enc_pdu_len; + if (err_pdu->len < min_size) { + RTR_DBG1("PDU is too small to contain erroneous PDU!"); + break; + } + + // Check if the the PDU really contains the error msg + uint32_t err_msg_len = ntohl(*((uint32_t *)(err_pdu->rest + enc_pdu_len))); + + RTR_DBG("err_msg_len: %u", err_msg_len); + min_size += err_msg_len; + if (err_pdu->len != min_size) { + RTR_DBG1("PDU is too small to contain error_msg!"); + break; + } + + retval = true; + break; + case SERIAL_QUERY: + if (sizeof(struct pdu_serial_query) == pdu->len) + retval = true; + break; + case RESET_QUERY: + if (sizeof(struct pdu_reset_query) == pdu->len) + retval = true; + break; + case RESERVED: + default: + RTR_DBG1("PDU type is unknown or reserved!"); + retval = false; + break; + } + +#ifndef NDEBUG + if (!retval) + RTR_DBG1("Received malformed PDU!"); +#endif + + return retval; +} + +static int rtr_send_pdu(const struct rtr_socket *rtr_socket, const void *pdu, const unsigned int len) +{ + char pdu_converted[len]; + + memcpy(pdu_converted, pdu, len); + rtr_pdu_to_network_byte_order(pdu_converted); + if (rtr_socket->state == RTR_SHUTDOWN) + return RTR_ERROR; + const int rtval = tr_send_all(rtr_socket->tr_socket, pdu_converted, len, RTR_SEND_TIMEOUT); + + if (rtval > 0) + return RTR_SUCCESS; + if (rtval == TR_WOULDBLOCK) { + RTR_DBG1("send would block"); + return RTR_ERROR; + } + + RTR_DBG1("Error sending PDU"); + return RTR_ERROR; +} + +/* + * if RTR_ERROR was returned a error PDU was sent, and the socket state changed + * @param pdu_len must >= RTR_MAX_PDU_LEN Bytes + * @return RTR_SUCCESS + * @return RTR_ERROR, error pdu was sent and socket_state changed + * @return TR_WOULDBLOCK + * \post + * If RTR_SUCCESS is returned PDU points to a well formed PDU that has + * the appropriate size for the PDU type it pretend to be. Thus, casting it to + * the PDU type struct and using it is save. Furthermore all PDU field are + * in host-byte-order. + */ +static int rtr_receive_pdu(struct rtr_socket *rtr_socket, void *pdu, const size_t pdu_len, const time_t timeout) +{ + int error = RTR_SUCCESS; + + assert(pdu_len >= RTR_MAX_PDU_LEN); + + if (rtr_socket->state == RTR_SHUTDOWN) + return RTR_ERROR; + // receive packet header + error = tr_recv_all(rtr_socket->tr_socket, pdu, sizeof(struct pdu_header), timeout); + if (error < 0) + goto error; + else + error = RTR_SUCCESS; + + // header in hostbyte order, retain original received pdu, in case we need to detach it to an error pdu + struct pdu_header header; + + memcpy(&header, pdu, sizeof(header)); + rtr_pdu_header_to_host_byte_order(&header); + + // if header->len is < packet_header = corrupt data received + if (header.len < sizeof(header)) { + error = CORRUPT_DATA; + goto error; + } else if (header.len > RTR_MAX_PDU_LEN) { // PDU too big, > than MAX_PDU_LEN Bytes + error = PDU_TOO_BIG; + goto error; + } + + // Handle live downgrading + if (!rtr_socket->has_received_pdus) { + if (rtr_socket->version == RTR_PROTOCOL_VERSION_1 && header.ver == RTR_PROTOCOL_VERSION_0 && + header.type != ERROR) { + RTR_DBG("First received PDU is a version 0 PDU, downgrading to %u", RTR_PROTOCOL_VERSION_0); + rtr_socket->version = RTR_PROTOCOL_VERSION_0; + } + rtr_socket->has_received_pdus = true; + } + + // Handle wrong protocol version + // If it is a error PDU, it will be handled by rtr_handle_error_pdu + if (header.ver != rtr_socket->version && header.type != ERROR) { + error = UNEXPECTED_PROTOCOL_VERSION; + goto error; + } + + // receive packet payload + const unsigned int remaining_len = header.len - sizeof(header); + + if (remaining_len > 0) { + if (rtr_socket->state == RTR_SHUTDOWN) + return RTR_ERROR; + error = tr_recv_all(rtr_socket->tr_socket, (((char *)pdu) + sizeof(header)), remaining_len, + RTR_RECV_TIMEOUT); + if (error < 0) + goto error; + else + error = RTR_SUCCESS; + } + // copy header in host_byte_order to pdu + memcpy(pdu, &header, sizeof(header)); + + // Check if the header len value is valid + if (rtr_pdu_check_size(pdu) == false) { + // TODO Restore byteorder for sending error PDU + error = CORRUPT_DATA; + goto error; + } + // At this point it is save to cast and use the PDU + + rtr_pdu_footer_to_host_byte_order(pdu); + + // Here we should handle error PDUs instead of doing it in + // several other places... + + if (header.type == IPV4_PREFIX || header.type == IPV6_PREFIX) { + if (((struct pdu_ipv4 *)pdu)->zero != 0) + RTR_DBG1("Warning: Zero field of received Prefix PDU doesn't contain 0"); + } + if (header.type == ROUTER_KEY && ((struct pdu_router_key *)pdu)->zero != 0) + RTR_DBG1("Warning: ROUTER_KEY_PDU zero field is != 0"); + + return RTR_SUCCESS; + +error: + // send error msg to server, including unmodified pdu header(pdu variable instead header) + if (error == -1) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } else if (error == TR_WOULDBLOCK) { + RTR_DBG1("receive timeout expired"); + return TR_WOULDBLOCK; + } else if (error == TR_INTR) { + RTR_DBG1("receive call interrupted"); + return TR_INTR; + } else if (error == CORRUPT_DATA) { + RTR_DBG1("corrupt PDU received"); + const char txt[] = "corrupt data received, length value in PDU is too small"; + + rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt)); + } else if (error == PDU_TOO_BIG) { + RTR_DBG1("PDU too big"); + char txt[42]; + + snprintf(txt, sizeof(txt), "PDU too big, max. PDU size is: %u bytes", RTR_MAX_PDU_LEN); + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), CORRUPT_DATA, txt, sizeof(txt)); + } else if (error == UNSUPPORTED_PDU_TYPE) { + RTR_DBG("Unsupported PDU type (%u) received", header.type); + rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNSUPPORTED_PDU_TYPE, NULL, 0); + } else if (error == UNSUPPORTED_PROTOCOL_VER) { + RTR_DBG("PDU with unsupported Protocol version (%u) received", header.ver); + rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNSUPPORTED_PROTOCOL_VER, NULL, 0); + return RTR_ERROR; + } else if (error == UNEXPECTED_PROTOCOL_VERSION) { + RTR_DBG("PDU with unexpected Protocol version (%u) received", header.ver); + rtr_send_error_pdu_from_network(rtr_socket, pdu, sizeof(header), UNEXPECTED_PROTOCOL_VERSION, NULL, 0); + return RTR_ERROR; + } + + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; +} + +static int rtr_handle_error_pdu(struct rtr_socket *rtr_socket, const void *buf) +{ + RTR_DBG1("Error PDU received"); // TODO: append server ip & port + const struct pdu_error *pdu = buf; + + switch (pdu->error_code) { + case CORRUPT_DATA: + RTR_DBG1("Corrupt data received"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + case INTERNAL_ERROR: + RTR_DBG1("Internal error on server-side"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + case NO_DATA_AVAIL: + RTR_DBG1("No data available"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_DATA_AVAIL); + break; + case INVALID_REQUEST: + RTR_DBG1("Invalid request from client"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + case UNSUPPORTED_PROTOCOL_VER: + RTR_DBG1("Client uses unsupported protocol version"); + if (pdu->ver <= RTR_PROTOCOL_MAX_SUPPORTED_VERSION && pdu->ver >= RTR_PROTOCOL_MIN_SUPPORTED_VERSION && + pdu->ver < rtr_socket->version) { + RTR_DBG("Downgrading from %i to version %i", rtr_socket->version, pdu->ver); + rtr_socket->version = pdu->ver; + rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT); + } else { + RTR_DBG("Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, current version:%i, PDU version:%i", + rtr_socket->version, pdu->ver); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + break; + case UNSUPPORTED_PDU_TYPE: + RTR_DBG1("Client set unsupported PDU type"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + default: + RTR_DBG("error unknown, server sent unsupported error code %u", pdu->error_code); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + break; + } + + const uint32_t len_err_txt = *((uint32_t *)(pdu->rest + pdu->len_enc_pdu)); + + if (len_err_txt > 0) { + if ((sizeof(pdu->ver) + sizeof(pdu->type) + sizeof(pdu->error_code) + sizeof(pdu->len) + + sizeof(pdu->len_enc_pdu) + pdu->len_enc_pdu + 4 + len_err_txt) != pdu->len) { + RTR_DBG1("error: Length of error text contains an incorrect value"); + } else { + char *pdu_txt = (char *)pdu->rest + pdu->len_enc_pdu + sizeof(len_err_txt); + + RTR_DBG("Error PDU included the following error msg: \'%.*s\'", len_err_txt, pdu_txt); + } + } + + return RTR_SUCCESS; +} + +static int rtr_handle_cache_response_pdu(struct rtr_socket *rtr_socket, char *pdu) +{ + RTR_DBG1("Cache Response PDU received"); + struct pdu_cache_response *cr_pdu = (struct pdu_cache_response *)pdu; + // set connection session_id + if (rtr_socket->request_session_id) { + if (rtr_socket->last_update != 0) { + RTR_DBG1("Resetting Socket."); + + rtr_socket->last_update = 0; + rtr_socket->is_resetting = true; + } + rtr_socket->session_id = cr_pdu->session_id; + } else { + if (rtr_socket->session_id != cr_pdu->session_id) { + const char txt[] = + "Wrong session_id in Cache Response PDU"; //TODO: Appendrtr_socket->session_id to string + rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, CORRUPT_DATA, txt, sizeof(txt)); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + } + return RTR_SUCCESS; +} + +static void rtr_key_pdu_2_spki_record(const struct rtr_socket *rtr_socket, const struct pdu_router_key *pdu, + struct spki_record *entry, const enum pdu_type type) +{ + assert(type == ROUTER_KEY); + entry->asn = pdu->asn; + memcpy(entry->ski, pdu->ski, SKI_SIZE); + memcpy(entry->spki, pdu->spki, SPKI_SIZE); + entry->socket = rtr_socket; +} + +static void rtr_prefix_pdu_2_pfx_record(const struct rtr_socket *rtr_socket, const void *pdu, struct pfx_record *pfxr, + const enum pdu_type type) +{ + assert(type == IPV4_PREFIX || type == IPV6_PREFIX); + if (type == IPV4_PREFIX) { + const struct pdu_ipv4 *ipv4 = pdu; + + pfxr->prefix.u.addr4.addr = ipv4->prefix; + pfxr->asn = ipv4->asn; + pfxr->prefix.ver = LRTR_IPV4; + pfxr->min_len = ipv4->prefix_len; + pfxr->max_len = ipv4->max_prefix_len; + pfxr->socket = rtr_socket; + } else if (type == IPV6_PREFIX) { + const struct pdu_ipv6 *ipv6 = pdu; + + pfxr->asn = ipv6->asn; + pfxr->prefix.ver = LRTR_IPV6; + memcpy(pfxr->prefix.u.addr6.addr, ipv6->prefix, sizeof(pfxr->prefix.u.addr6.addr)); + pfxr->min_len = ipv6->prefix_len; + pfxr->max_len = ipv6->max_prefix_len; + pfxr->socket = rtr_socket; + } +} + +/* + * @brief Removes all Prefix from the pfx_table with flag field == ADD, ADDs all Prefix PDU to the pfx_table with flag + * field == REMOVE. + */ +static int rtr_undo_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, void *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + + assert(type == IPV4_PREFIX || type == IPV6_PREFIX); + + struct pfx_record pfxr; + + rtr_prefix_pdu_2_pfx_record(rtr_socket, pdu, &pfxr, type); + + int rtval = RTR_ERROR; + // invert add/remove operation + if (((struct pdu_ipv4 *)pdu)->flags == 1) + rtval = pfx_table_remove(pfx_table, &pfxr); + else if (((struct pdu_ipv4 *)pdu)->flags == 0) + rtval = pfx_table_add(pfx_table, &pfxr); + return rtval; +} + +/* + * @brief Removes router_key from the spki_table with flag field == ADD, ADDs router_key PDU to the spki_table with flag + * field == REMOVE. + */ +static int rtr_undo_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, void *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + + assert(type == ROUTER_KEY); + + struct spki_record entry; + + rtr_key_pdu_2_spki_record(rtr_socket, pdu, &entry, type); + + int rtval = RTR_ERROR; + // invert add/remove operation + if (((struct pdu_router_key *)pdu)->flags == 1) + rtval = spki_table_remove_entry(spki_table, &entry); + else if (((struct pdu_router_key *)pdu)->flags == 0) + rtval = spki_table_add_entry(spki_table, &entry); + return rtval; +} + +/* + * @brief Appends the Prefix PDU pdu to ary. + * + * @return RTR_SUCCESS On success + * @return RTR_ERROR On realloc failure + * @attention ary is not freed in this case, because it might contain data that is still needed + */ +static int rtr_store_prefix_pdu(struct rtr_socket *rtr_socket, const void *pdu, const unsigned int pdu_size, void **ary, + unsigned int *ind, unsigned int *size) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + + assert(type == IPV4_PREFIX || type == IPV6_PREFIX); + if ((*ind) >= *size) { + *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE; + void *tmp = lrtr_realloc(*ary, *size * pdu_size); + + if (!tmp) { + const char txt[] = "Realloc failed"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt)); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + *ary = tmp; + } + if (type == IPV4_PREFIX) { + struct pdu_ipv4 *ary_ipv4 = *ary; + + memcpy(ary_ipv4 + *ind, pdu, pdu_size); + } else if (type == IPV6_PREFIX) { + struct pdu_ipv6 *ary_ipv6 = *ary; + + memcpy(ary_ipv6 + *ind, pdu, pdu_size); + } + (*ind)++; + return RTR_SUCCESS; +} + +/* + * @brief Appends the router key to ary. + * + * @return RTR_SUCCESS On success + * @return RTR_ERROR On realloc failure + * @attention ary is not freed in this case, because it might contain data that is still needed + */ +static int rtr_store_router_key_pdu(struct rtr_socket *rtr_socket, const void *pdu, const unsigned int pdu_size, + struct pdu_router_key **ary, unsigned int *ind, unsigned int *size) +{ + assert(rtr_get_pdu_type(pdu) == ROUTER_KEY); + + if ((*ind) >= *size) { + *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE; + void *tmp = lrtr_realloc(*ary, *size * pdu_size); + + if (!tmp) { + const char txt[] = "Realloc failed"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt)); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + *ary = tmp; + } + + memcpy((struct pdu_router_key *)*ary + *ind, pdu, pdu_size); + (*ind)++; + return RTR_SUCCESS; +} + +static int rtr_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, const void *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + + assert(type == IPV4_PREFIX || type == IPV6_PREFIX); + + struct pfx_record pfxr; + size_t pdu_size = (type == IPV4_PREFIX ? sizeof(struct pdu_ipv4) : sizeof(struct pdu_ipv6)); + + rtr_prefix_pdu_2_pfx_record(rtr_socket, pdu, &pfxr, type); + + int rtval; + + if (((struct pdu_ipv4 *)pdu)->flags == 1) { + rtval = pfx_table_add(pfx_table, &pfxr); + } else if (((struct pdu_ipv4 *)pdu)->flags == 0) { + rtval = pfx_table_remove(pfx_table, &pfxr); + } else { + const char txt[] = "Prefix PDU with invalid flags value received"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt)); + return RTR_ERROR; + } + + if (rtval == PFX_DUPLICATE_RECORD) { + char ip[INET6_ADDRSTRLEN]; + + lrtr_ip_addr_to_str(&(pfxr.prefix), ip, INET6_ADDRSTRLEN); + RTR_DBG("Duplicate Announcement for record: %s/%u-%u, ASN: %u, received", ip, pfxr.min_len, + pfxr.max_len, pfxr.asn); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, DUPLICATE_ANNOUNCEMENT, NULL, 0); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } else if (rtval == PFX_RECORD_NOT_FOUND) { + RTR_DBG1("Withdrawal of unknown record"); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, WITHDRAWAL_OF_UNKNOWN_RECORD, NULL, 0); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } else if (rtval == PFX_ERROR) { + const char txt[] = "PFX_TABLE Error"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt)); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + + return RTR_SUCCESS; +} + +static int rtr_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, const void *pdu) +{ + const enum pdu_type type = rtr_get_pdu_type(pdu); + + assert(type == ROUTER_KEY); + + struct spki_record entry; + + size_t pdu_size = sizeof(struct pdu_router_key); + + rtr_key_pdu_2_spki_record(rtr_socket, pdu, &entry, type); + + int rtval; + + if (((struct pdu_router_key *)pdu)->flags == 1) { + rtval = spki_table_add_entry(spki_table, &entry); + + } else if (((struct pdu_router_key *)pdu)->flags == 0) { + rtval = spki_table_remove_entry(spki_table, &entry); + + } else { + const char txt[] = "Router Key PDU with invalid flags value received"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, CORRUPT_DATA, txt, sizeof(txt)); + return RTR_ERROR; + } + + if (rtval == SPKI_DUPLICATE_RECORD) { + // TODO: This debug message isn't working yet, how to display SKI/SPKI without %x? + RTR_DBG("Duplicate Announcement for router key: ASN: %u received", entry.asn); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, DUPLICATE_ANNOUNCEMENT, NULL, 0); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } else if (rtval == SPKI_RECORD_NOT_FOUND) { + RTR_DBG1("Withdrawal of unknown router key"); + rtr_send_error_pdu_from_host(rtr_socket, pdu, pdu_size, WITHDRAWAL_OF_UNKNOWN_RECORD, NULL, 0); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } else if (rtval == SPKI_ERROR) { + const char txt[] = "spki_table Error"; + + RTR_DBG("%s", txt); + rtr_send_error_pdu_from_host(rtr_socket, NULL, 0, INTERNAL_ERROR, txt, sizeof(txt)); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + + return RTR_SUCCESS; +} + +void recv_loop_cleanup(void *p) +{ + struct recv_loop_cleanup_args *args = p; + + lrtr_free(args->ipv4_pdus); + lrtr_free(args->ipv6_pdus); + lrtr_free(args->router_key_pdus); +} + +/* WARNING: This Function has cancelable sections*/ +static int rtr_sync_receive_and_store_pdus(struct rtr_socket *rtr_socket) +{ + char pdu[RTR_MAX_PDU_LEN]; + enum pdu_type type; + int retval = RTR_SUCCESS; + + struct pdu_ipv6 *ipv6_pdus = NULL; + unsigned int ipv6_pdus_nindex = 0; // next free index in ipv6_pdus + unsigned int ipv6_pdus_size = 0; + + struct pdu_ipv4 *ipv4_pdus = NULL; + unsigned int ipv4_pdus_size = 0; + unsigned int ipv4_pdus_nindex = 0; // next free index in ipv4_pdus + + struct pdu_router_key *router_key_pdus = NULL; + unsigned int router_key_pdus_size = 0; + unsigned int router_key_pdus_nindex = 0; + + struct pfx_table *pfx_shadow_table = NULL; + struct spki_table *spki_shadow_table = NULL; + + int oldcancelstate; + struct recv_loop_cleanup_args cleanup_args = { + .ipv4_pdus = ipv4_pdus, .ipv6_pdus = ipv6_pdus, .router_key_pdus = router_key_pdus}; + + // receive LRTR_IPV4/IPV6 PDUs till EOD + do { + pthread_cleanup_push(recv_loop_cleanup, &cleanup_args); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + retval = rtr_receive_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, RTR_RECV_TIMEOUT); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + pthread_cleanup_pop(0); + + if (retval == TR_WOULDBLOCK) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + retval = RTR_ERROR; + goto cleanup; + } else if (retval < 0) { + retval = RTR_ERROR; + goto cleanup; + } + + type = rtr_get_pdu_type(pdu); + if (type == IPV4_PREFIX) { + if (rtr_store_prefix_pdu(rtr_socket, pdu, sizeof(*ipv4_pdus), (void **)&ipv4_pdus, + &ipv4_pdus_nindex, &ipv4_pdus_size) == RTR_ERROR) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } else if (type == IPV6_PREFIX) { + if (rtr_store_prefix_pdu(rtr_socket, pdu, sizeof(*ipv6_pdus), (void **)&ipv6_pdus, + &ipv6_pdus_nindex, &ipv6_pdus_size) == RTR_ERROR) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } else if (type == ROUTER_KEY) { + if (rtr_store_router_key_pdu(rtr_socket, pdu, sizeof(*router_key_pdus), &router_key_pdus, + &router_key_pdus_nindex, &router_key_pdus_size) == RTR_ERROR) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } else if (type == EOD) { + RTR_DBG1("EOD PDU received."); + struct pdu_end_of_data_v0 *eod_pdu = (struct pdu_end_of_data_v0 *)pdu; + + if (eod_pdu->session_id != rtr_socket->session_id) { + char txt[67]; + + snprintf(txt, sizeof(txt), + "Expected session_id: %u, received session_id. %u in EOD PDU", + rtr_socket->session_id, eod_pdu->session_id); + rtr_send_error_pdu_from_host(rtr_socket, pdu, RTR_MAX_PDU_LEN, CORRUPT_DATA, txt, + strlen(txt) + 1); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + + if (eod_pdu->ver == RTR_PROTOCOL_VERSION_1 && + rtr_socket->iv_mode != RTR_INTERVAL_MODE_IGNORE_ANY) { + int interv_retval; + + interv_retval = + rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode, + ((struct pdu_end_of_data_v1 *)pdu)->expire_interval, + RTR_INTERVAL_TYPE_EXPIRATION); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu, + ((struct pdu_end_of_data_v1 *)pdu)->expire_interval, + RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX); + retval = RTR_ERROR; + goto cleanup; + } + + interv_retval = + rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode, + ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu, + ((struct pdu_end_of_data_v1 *)pdu)->refresh_interval, + RTR_REFRESH_MIN, RTR_REFRESH_MAX); + retval = RTR_ERROR; + goto cleanup; + } + + interv_retval = rtr_check_interval_option( + rtr_socket, rtr_socket->iv_mode, + ((struct pdu_end_of_data_v1 *)pdu)->retry_interval, RTR_INTERVAL_TYPE_RETRY); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu, + ((struct pdu_end_of_data_v1 *)pdu)->retry_interval, + RTR_RETRY_MIN, RTR_RETRY_MAX); + retval = RTR_ERROR; + goto cleanup; + } + + RTR_DBG("New interval values: expire_interval:%u, refresh_interval:%u, retry_interval:%u", + rtr_socket->expire_interval, rtr_socket->refresh_interval, + rtr_socket->retry_interval); + } + + struct pfx_table *pfx_update_table; + struct spki_table *spki_update_table; + + if (rtr_socket->is_resetting) { + RTR_DBG1("Reset in progress creating shadow table for atomic reset"); + pfx_shadow_table = lrtr_malloc(sizeof(struct pfx_table)); + if (!pfx_shadow_table) { + RTR_DBG1("Memory allocation for pfx shadow table failed"); + retval = RTR_ERROR; + goto cleanup; + } + + pfx_table_init(pfx_shadow_table, NULL); + pfx_update_table = pfx_shadow_table; + if (pfx_table_copy_except_socket(rtr_socket->pfx_table, pfx_update_table, rtr_socket)) { + RTR_DBG1("Creation of pfx shadow table failed"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + + spki_shadow_table = lrtr_malloc(sizeof(struct spki_table)); + if (!spki_shadow_table) { + RTR_DBG1("Memory allocation for spki shadow table failed"); + retval = RTR_ERROR; + goto cleanup; + } + spki_table_init(spki_shadow_table, NULL); + spki_update_table = spki_shadow_table; + if (spki_table_copy_except_socket(rtr_socket->spki_table, spki_update_table, + rtr_socket) != SPKI_SUCCESS) { + RTR_DBG1("Creation of spki shadow table failed"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + + RTR_DBG1("Shadow table created"); + } else { + pfx_update_table = rtr_socket->pfx_table; + spki_update_table = rtr_socket->spki_table; + } + + retval = PFX_SUCCESS; + // add all IPv4 prefix pdu to the pfx_table + for (unsigned int i = 0; i < ipv4_pdus_nindex; i++) { + if (rtr_update_pfx_table(rtr_socket, pfx_update_table, &(ipv4_pdus[i])) == PFX_ERROR) { + // undo all record updates, except the last which produced the error + RTR_DBG("Error during data synchronisation, recovering Serial Nr. %u state", + rtr_socket->serial_number); + for (unsigned int j = 0; j < i && retval == PFX_SUCCESS; j++) + retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table, + &(ipv4_pdus[j])); + if (retval == RTR_ERROR) { + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all records"); + pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket); + rtr_socket->request_session_id = true; + } + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } + RTR_DBG1("v4 prefixes added"); + // add all IPv6 prefix pdu to the pfx_table + for (unsigned int i = 0; i < ipv6_pdus_nindex; i++) { + if (rtr_update_pfx_table(rtr_socket, pfx_update_table, &(ipv6_pdus[i])) == PFX_ERROR) { + // undo all record updates if error occurred + RTR_DBG("Error during data synchronisation, recovering Serial Nr. %u state", + rtr_socket->serial_number); + for (unsigned int j = 0; j < ipv4_pdus_nindex && retval == PFX_SUCCESS; j++) + retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table, + &(ipv4_pdus[j])); + for (unsigned int j = 0; j < i && retval == PFX_SUCCESS; j++) + retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table, + &(ipv6_pdus[j])); + if (retval == PFX_ERROR) { + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all records"); + pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket); + rtr_socket->request_session_id = true; + } + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } + + RTR_DBG1("v6 prefixes added"); + // add all router key pdu to the spki_table + for (unsigned int i = 0; i < router_key_pdus_nindex; i++) { + if (rtr_update_spki_table(rtr_socket, spki_update_table, &(router_key_pdus[i])) == + SPKI_ERROR) { + RTR_DBG("Error during router key data synchronisation, recovering Serial Nr. %u state", + rtr_socket->serial_number); + for (unsigned int j = 0; j < ipv4_pdus_nindex && retval == PFX_SUCCESS; j++) + retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table, + &(ipv4_pdus[j])); + for (unsigned int j = 0; j < ipv6_pdus_nindex && retval == PFX_SUCCESS; j++) + retval = rtr_undo_update_pfx_table(rtr_socket, pfx_update_table, + &(ipv6_pdus[j])); + for (unsigned int j = 0; + // cppcheck-suppress duplicateExpression + j < i && (retval == PFX_SUCCESS || retval == SPKI_SUCCESS); j++) + retval = rtr_undo_update_spki_table(rtr_socket, spki_update_table, + &(router_key_pdus[j])); + // cppcheck-suppress duplicateExpression + if (retval == RTR_ERROR || retval == SPKI_ERROR) { + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all key entries"); + spki_table_src_remove(spki_update_table, rtr_socket); + rtr_socket->request_session_id = true; + } + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + retval = RTR_ERROR; + goto cleanup; + } + } + RTR_DBG1("spki data added"); + if (rtr_socket->is_resetting) { + RTR_DBG1("Reset finished. Swapping new table in."); + pfx_table_swap(rtr_socket->pfx_table, pfx_shadow_table); + spki_table_swap(rtr_socket->spki_table, spki_shadow_table); + + if (rtr_socket->pfx_table->update_fp) { + RTR_DBG1("Calculating and notifying pfx diff"); + pfx_table_notify_diff(rtr_socket->pfx_table, pfx_shadow_table, rtr_socket); + } else { + RTR_DBG1("No pfx update callback. Skipping diff"); + } + + if (rtr_socket->spki_table->update_fp) { + RTR_DBG1("Calculating and notifying spki diff"); + spki_table_notify_diff(rtr_socket->spki_table, spki_shadow_table, rtr_socket); + } else { + RTR_DBG1("No spki update callback. Skipping diff"); + } + } + + rtr_socket->serial_number = eod_pdu->sn; + RTR_DBG("Sync successful, received %u Prefix PDUs, %u Router Key PDUs, session_id: %u, SN: %u", + (ipv4_pdus_nindex + ipv6_pdus_nindex), router_key_pdus_nindex, rtr_socket->session_id, + rtr_socket->serial_number); + goto cleanup; + } else if (type == ERROR) { + rtr_handle_error_pdu(rtr_socket, pdu); + retval = RTR_ERROR; + goto cleanup; + } else if (type == SERIAL_NOTIFY) { + RTR_DBG1("Ignoring Serial Notify"); + } else { + RTR_DBG("Received unexpected PDU (Type: %u)", ((struct pdu_header *)pdu)->type); + const char txt[] = "Unexpected PDU received during data synchronisation"; + + rtr_send_error_pdu_from_host(rtr_socket, pdu, sizeof(struct pdu_header), CORRUPT_DATA, txt, + sizeof(txt)); + retval = RTR_ERROR; + goto cleanup; + } + } while (type != EOD); + +cleanup: + + if (rtr_socket->is_resetting) { + RTR_DBG1("Freeing shadow tables."); + if (pfx_shadow_table) { + pfx_table_free_without_notify(pfx_shadow_table); + lrtr_free(pfx_shadow_table); + } + + if (spki_shadow_table) { + spki_table_free_without_notify(spki_shadow_table); + lrtr_free(spki_shadow_table); + } + rtr_socket->is_resetting = false; + } + + lrtr_free(router_key_pdus); + lrtr_free(ipv6_pdus); + lrtr_free(ipv4_pdus); + return retval; +} + +/* WARNING: This Function has cancelable sections */ +int rtr_sync(struct rtr_socket *rtr_socket) +{ + char pdu[RTR_MAX_PDU_LEN]; + enum pdu_type type; + + int oldcancelstate; + + do { + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int rtval = rtr_receive_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, RTR_RECV_TIMEOUT); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + // If the cache has closed the connection and we don't have a + // session_id (no packages where exchanged) we should downgrade. + if (rtval == TR_CLOSED && rtr_socket->request_session_id) { + RTR_DBG1("The cache server closed the connection and we have no session_id!"); + if (rtr_socket->version > RTR_PROTOCOL_MIN_SUPPORTED_VERSION) { + RTR_DBG("Downgrading from %i to version %i", rtr_socket->version, + rtr_socket->version - 1); + rtr_socket->version = rtr_socket->version - 1; + rtr_change_socket_state(rtr_socket, RTR_FAST_RECONNECT); + return RTR_ERROR; + } + } + + if (rtval == TR_WOULDBLOCK) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } else if (rtval < 0) { + return RTR_ERROR; + } + + type = rtr_get_pdu_type(pdu); + if (type == SERIAL_NOTIFY) + RTR_DBG1("Ignoring Serial Notify"); + + } while (type == SERIAL_NOTIFY); + + switch (type) { + case ERROR: + rtr_handle_error_pdu(rtr_socket, pdu); + return RTR_ERROR; + case CACHE_RESET: + RTR_DBG1("Cache Reset PDU received"); + rtr_change_socket_state(rtr_socket, RTR_ERROR_NO_INCR_UPDATE_AVAIL); + return RTR_ERROR; + case CACHE_RESPONSE: + rtr_handle_cache_response_pdu(rtr_socket, pdu); + break; + default: + RTR_DBG("Expected Cache Response PDU but received PDU Type (Type: %u)", + ((struct pdu_header *)pdu)->type); + const char txt[] = "Unexpected PDU received in data synchronisation"; + + rtr_send_error_pdu_from_host(rtr_socket, pdu, sizeof(struct pdu_header), CORRUPT_DATA, txt, + sizeof(txt)); + return RTR_ERROR; + } + + // Receive all PDUs until EOD PDU + if (rtr_sync_receive_and_store_pdus(rtr_socket) == RTR_ERROR) + return RTR_ERROR; + + rtr_socket->request_session_id = false; + if (rtr_set_last_update(rtr_socket) == RTR_ERROR) + return RTR_ERROR; + + return RTR_SUCCESS; +} + +int rtr_wait_for_sync(struct rtr_socket *rtr_socket) +{ + char pdu[RTR_MAX_PDU_LEN]; + + time_t cur_time; + + lrtr_get_monotonic_time(&cur_time); + time_t wait = (rtr_socket->last_update + rtr_socket->refresh_interval) - cur_time; + + if (wait < 0) + wait = 0; + + RTR_DBG("waiting %jd sec. till next sync", (intmax_t)wait); + const int rtval = rtr_receive_pdu(rtr_socket, pdu, sizeof(pdu), wait); + + if (rtval >= 0) { + enum pdu_type type = rtr_get_pdu_type(pdu); + + if (type == SERIAL_NOTIFY) { + RTR_DBG("Serial Notify received (%u)", ((struct pdu_serial_notify *)pdu)->sn); + return RTR_SUCCESS; + } + } else if (rtval == TR_WOULDBLOCK) { + RTR_DBG1("Refresh interval expired"); + return RTR_SUCCESS; + } + return RTR_ERROR; +} + +static int rtr_send_error_pdu(const struct rtr_socket *rtr_socket, const void *erroneous_pdu, + const uint32_t erroneous_pdu_len, const enum pdu_error_type error, const char *err_text, + const uint32_t err_text_len) +{ + struct pdu_error *err_pdu; + unsigned int msg_size = sizeof(struct pdu_error) + 4 + erroneous_pdu_len + err_text_len; + uint8_t msg[msg_size]; + + // don't send errors for erroneous error PDUs + if (erroneous_pdu_len >= 2) { + if (rtr_get_pdu_type(erroneous_pdu) == ERROR) { + RTR_DBG1("Don't send errors for erroneous error PDUs"); + return RTR_SUCCESS; + } + } + + err_pdu = (struct pdu_error *)msg; + err_pdu->ver = rtr_socket->version; + err_pdu->type = ERROR; + err_pdu->error_code = error; + err_pdu->len = msg_size; + + err_pdu->len_enc_pdu = erroneous_pdu_len; + if (erroneous_pdu_len > 0) + memcpy(err_pdu->rest, erroneous_pdu, erroneous_pdu_len); + + *((uint32_t *)(err_pdu->rest + erroneous_pdu_len)) = err_text_len; + if (err_text_len > 0) + memcpy(err_pdu->rest + erroneous_pdu_len + 4, err_text, err_text_len); + + return rtr_send_pdu(rtr_socket, msg, msg_size); +} + +static int interval_send_error_pdu(struct rtr_socket *rtr_socket, void *pdu, uint32_t interval, uint16_t minimum, + uint32_t maximum) +{ + RTR_DBG("Received expiration value out of range. Was %u, must be between %u and %u.", interval, minimum, + maximum); + const char txt[] = "Interval value out of range"; + + return rtr_send_error_pdu(rtr_socket, pdu, RTR_MAX_PDU_LEN, CORRUPT_DATA, txt, strlen(txt) + 1); +} + +static int rtr_send_error_pdu_from_network(const struct rtr_socket *rtr_socket, const void *erroneous_pdu, + const uint32_t erroneous_pdu_len, const enum pdu_error_type error, + const char *err_text, const uint32_t err_text_len) +{ + return rtr_send_error_pdu(rtr_socket, erroneous_pdu, erroneous_pdu_len, error, err_text, err_text_len); +} + +static int rtr_send_error_pdu_from_host(const struct rtr_socket *rtr_socket, const void *erroneous_pdu, + const uint32_t erroneous_pdu_len, const enum pdu_error_type error, + const char *err_text, const uint32_t err_text_len) +{ + char pdu[erroneous_pdu_len]; + + memcpy(&pdu, erroneous_pdu, erroneous_pdu_len); + + if (erroneous_pdu_len == sizeof(struct pdu_header)) + rtr_pdu_header_to_network_byte_order(&pdu); + else if (erroneous_pdu_len > sizeof(struct pdu_header)) + rtr_pdu_to_network_byte_order(&pdu); + else + return RTR_ERROR; + + return rtr_send_error_pdu(rtr_socket, &pdu, erroneous_pdu_len, error, err_text, err_text_len); +} + +int rtr_send_serial_query(struct rtr_socket *rtr_socket) +{ + struct pdu_serial_query pdu; + + pdu.ver = rtr_socket->version; + pdu.type = SERIAL_QUERY; + pdu.session_id = rtr_socket->session_id; + pdu.len = sizeof(pdu); + pdu.sn = rtr_socket->serial_number; + + RTR_DBG("sending serial query, SN: %u", rtr_socket->serial_number); + if (rtr_send_pdu(rtr_socket, &pdu, sizeof(pdu)) != RTR_SUCCESS) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } + return RTR_SUCCESS; +} + +int rtr_send_reset_query(struct rtr_socket *rtr_socket) +{ + RTR_DBG1("Sending reset query"); + struct pdu_reset_query pdu; + + pdu.ver = rtr_socket->version; + pdu.type = 2; + pdu.flags = 0; + pdu.len = 8; + + if (rtr_send_pdu(rtr_socket, &pdu, sizeof(pdu)) != RTR_SUCCESS) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + return RTR_ERROR; + } + return RTR_SUCCESS; +} diff --git a/rtrlib/rtr/packets_private.h b/rtrlib/rtr/packets_private.h new file mode 100644 index 0000000..c8d5093 --- /dev/null +++ b/rtrlib/rtr/packets_private.h @@ -0,0 +1,32 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTR_PACKETS_PRIVATE_H +#define RTR_PACKETS_PRIVATE_H + +#include "rtrlib/rtr/rtr_private.h" + +#include <arpa/inet.h> + +// error pdu: header(8) + len(4) + ipv6_pdu(32) + len(4) + 400*8 (400 char text) +static const unsigned int RTR_MAX_PDU_LEN = 3248; +static const unsigned int RTR_RECV_TIMEOUT = 60; +static const unsigned int RTR_SEND_TIMEOUT = 60; + +void __attribute__((weak)) +rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state); +int rtr_sync(struct rtr_socket *rtr_socket); +int rtr_wait_for_sync(struct rtr_socket *rtr_socket); +int rtr_send_serial_query(struct rtr_socket *rtr_socket); +int rtr_send_reset_query(struct rtr_socket *rtr_socket); +int rtr_check_interval_range(uint32_t interval, uint32_t minimum, uint32_t maximum); +void apply_interval_value(struct rtr_socket *rtr_socket, uint32_t interval, enum rtr_interval_type type); +int rtr_check_interval_option(struct rtr_socket *rtr_socket, int interval_mode, uint32_t interval, + enum rtr_interval_type type); +#endif diff --git a/rtrlib/rtr/rtr.c b/rtrlib/rtr/rtr.c new file mode 100644 index 0000000..086c953 --- /dev/null +++ b/rtrlib/rtr/rtr.c @@ -0,0 +1,272 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtr_private.h" + +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/rtr/packets_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <pthread.h> +#include <signal.h> +#include <unistd.h> + +static void rtr_purge_outdated_records(struct rtr_socket *rtr_socket); +static void *rtr_fsm_start(struct rtr_socket *rtr_socket); + +static const char *socket_str_states[] = {[RTR_CONNECTING] = "RTR_CONNECTING", + [RTR_ESTABLISHED] = "RTR_ESTABLISHED", + [RTR_RESET] = "RTR_RESET", + [RTR_SYNC] = "RTR_SYNC", + [RTR_FAST_RECONNECT] = "RTR_FAST_RECONNECT", + [RTR_ERROR_NO_DATA_AVAIL] = "RTR_ERROR_NO_DATA_AVAIL", + [RTR_ERROR_NO_INCR_UPDATE_AVAIL] = "RTR_ERROR_NO_INCR_UPDATE_AVAIL", + [RTR_ERROR_FATAL] = "RTR_ERROR_FATAL", + [RTR_ERROR_TRANSPORT] = "RTR_ERROR_TRANSPORT", + [RTR_SHUTDOWN] = "RTR_SHUTDOWN"}; + +int rtr_init(struct rtr_socket *rtr_socket, struct tr_socket *tr, struct pfx_table *pfx_table, + struct spki_table *spki_table, const unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval, enum rtr_interval_mode iv_mode, rtr_connection_state_fp fp, + void *fp_param_config, void *fp_param_group) +{ + if (tr) + rtr_socket->tr_socket = tr; + + // Check if one of the intervals is not in range of the predefined values. + if (rtr_check_interval_range(refresh_interval, RTR_REFRESH_MIN, RTR_REFRESH_MAX) != RTR_INSIDE_INTERVAL_RANGE || + rtr_check_interval_range(expire_interval, RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX) != + RTR_INSIDE_INTERVAL_RANGE || + rtr_check_interval_range(retry_interval, RTR_RETRY_MIN, RTR_RETRY_MAX) != RTR_INSIDE_INTERVAL_RANGE) { + RTR_DBG("Interval value not in range."); + return RTR_INVALID_PARAM; + } + rtr_socket->refresh_interval = refresh_interval; + rtr_socket->expire_interval = expire_interval; + rtr_socket->retry_interval = retry_interval; + rtr_socket->iv_mode = iv_mode; + + rtr_socket->state = RTR_CLOSED; + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + rtr_socket->pfx_table = pfx_table; + rtr_socket->spki_table = spki_table; + rtr_socket->connection_state_fp = fp; + rtr_socket->connection_state_fp_param_config = fp_param_config; + rtr_socket->connection_state_fp_param_group = fp_param_group; + rtr_socket->thread_id = 0; + rtr_socket->version = RTR_PROTOCOL_MAX_SUPPORTED_VERSION; + rtr_socket->has_received_pdus = false; + rtr_socket->is_resetting = false; + return RTR_SUCCESS; +} + +int rtr_start(struct rtr_socket *rtr_socket) +{ + if (rtr_socket->thread_id) + return RTR_ERROR; + + int rtval = pthread_create(&(rtr_socket->thread_id), NULL, (void *(*)(void *)) &rtr_fsm_start, rtr_socket); + + if (rtval == 0) + return RTR_SUCCESS; + return RTR_ERROR; +} + +void rtr_purge_outdated_records(struct rtr_socket *rtr_socket) +{ + if (rtr_socket->last_update == 0) + return; + time_t cur_time; + int rtval = lrtr_get_monotonic_time(&cur_time); + + if (rtval == -1 || (rtr_socket->last_update + rtr_socket->expire_interval) < cur_time) { + if (rtval == -1) + RTR_DBG1("get_monotic_time(..) failed"); + pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket); + RTR_DBG1("Removed outdated records from pfx_table"); + spki_table_src_remove(rtr_socket->spki_table, rtr_socket); + RTR_DBG1("Removed outdated router keys from spki_table"); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + rtr_socket->is_resetting = true; + } +} + +/* WARNING: This Function has cancelable sections*/ +void *rtr_fsm_start(struct rtr_socket *rtr_socket) +{ + if (rtr_socket->state == RTR_SHUTDOWN) + return NULL; + + // We don't care about the old state, but POSIX demands a non null value for setcancelstate + int oldcancelstate; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + rtr_socket->state = RTR_CONNECTING; + while (1) { + if (rtr_socket->state == RTR_CONNECTING) { + RTR_DBG1("State: RTR_CONNECTING"); + rtr_socket->has_received_pdus = false; + + // old pfx_record could exists in the pfx_table, check if they are too old and must be removed + // old key_entry could exists in the spki_table, check if they are too old and must be removed + rtr_purge_outdated_records(rtr_socket); + + if (tr_open(rtr_socket->tr_socket) == TR_ERROR) { + rtr_change_socket_state(rtr_socket, RTR_ERROR_TRANSPORT); + } else if (rtr_socket->request_session_id) { + // change to state RESET, if socket doesn't have a session_id + rtr_change_socket_state(rtr_socket, RTR_RESET); + } else { + // if we already have a session_id, send a serial query and start to sync + if (rtr_send_serial_query(rtr_socket) == RTR_SUCCESS) + rtr_change_socket_state(rtr_socket, RTR_SYNC); + else + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + } + } + + else if (rtr_socket->state == RTR_RESET) { + RTR_DBG1("State: RTR_RESET"); + if (rtr_send_reset_query(rtr_socket) == RTR_SUCCESS) { + RTR_DBG1("rtr_start: reset pdu sent"); + rtr_change_socket_state(rtr_socket, + RTR_SYNC); // start to sync after connection is established + } + } + + else if (rtr_socket->state == RTR_SYNC) { + RTR_DBG1("State: RTR_SYNC"); + if (rtr_sync(rtr_socket) == RTR_SUCCESS) + rtr_change_socket_state( + rtr_socket, + RTR_ESTABLISHED); // wait for next sync after first successful sync + } + + else if (rtr_socket->state == RTR_ESTABLISHED) { + RTR_DBG1("State: RTR_ESTABLISHED"); + + // Allow thread cancellation for recv code path only. + // This should be enough since we spend most of the time blocking on recv + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int ret = rtr_wait_for_sync( + rtr_socket); // blocks till expire_interval is expired or PDU was received + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (ret == RTR_SUCCESS) { // send serial query + if (rtr_send_serial_query(rtr_socket) == RTR_SUCCESS) + rtr_change_socket_state(rtr_socket, RTR_SYNC); + } + } + + else if (rtr_socket->state == RTR_FAST_RECONNECT) { + RTR_DBG1("State: RTR_FAST_RECONNECT"); + tr_close(rtr_socket->tr_socket); + rtr_change_socket_state(rtr_socket, RTR_CONNECTING); + } + + else if (rtr_socket->state == RTR_ERROR_NO_DATA_AVAIL) { + RTR_DBG1("State: RTR_ERROR_NO_DATA_AVAIL"); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_change_socket_state(rtr_socket, RTR_RESET); + sleep(rtr_socket->retry_interval); + rtr_purge_outdated_records(rtr_socket); + } + + else if (rtr_socket->state == RTR_ERROR_NO_INCR_UPDATE_AVAIL) { + RTR_DBG1("State: RTR_ERROR_NO_INCR_UPDATE_AVAIL"); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_change_socket_state(rtr_socket, RTR_RESET); + rtr_purge_outdated_records(rtr_socket); + } + + else if (rtr_socket->state == RTR_ERROR_TRANSPORT) { + RTR_DBG1("State: RTR_ERROR_TRANSPORT"); + tr_close(rtr_socket->tr_socket); + rtr_change_socket_state(rtr_socket, RTR_CONNECTING); + RTR_DBG("Waiting %u", rtr_socket->retry_interval); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + sleep(rtr_socket->retry_interval); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + } + + else if (rtr_socket->state == RTR_ERROR_FATAL) { + RTR_DBG1("State: RTR_ERROR_FATAL"); + tr_close(rtr_socket->tr_socket); + rtr_change_socket_state(rtr_socket, RTR_CONNECTING); + RTR_DBG("Waiting %u", rtr_socket->retry_interval); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + sleep(rtr_socket->retry_interval); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + } + + else if (rtr_socket->state == RTR_SHUTDOWN) { + RTR_DBG1("State: RTR_SHUTDOWN"); + pthread_exit(NULL); + } + } +} + +void rtr_stop(struct rtr_socket *rtr_socket) +{ + RTR_DBG("%s()", __func__); + rtr_change_socket_state(rtr_socket, RTR_SHUTDOWN); + if (rtr_socket->thread_id != 0) { + RTR_DBG1("pthread_cancel()"); + pthread_cancel(rtr_socket->thread_id); + RTR_DBG1("pthread_join()"); + pthread_join(rtr_socket->thread_id, NULL); + + tr_close(rtr_socket->tr_socket); + rtr_socket->request_session_id = true; + rtr_socket->serial_number = 0; + rtr_socket->last_update = 0; + pfx_table_src_remove(rtr_socket->pfx_table, rtr_socket); + spki_table_src_remove(rtr_socket->spki_table, rtr_socket); + rtr_socket->thread_id = 0; + } + RTR_DBG1("Socket shut down"); +} + +RTRLIB_EXPORT const char *rtr_state_to_str(enum rtr_socket_state state) +{ + return socket_str_states[state]; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT enum rtr_interval_mode rtr_get_interval_mode(struct rtr_socket *rtr_socket) +{ + return rtr_socket->iv_mode; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void rtr_set_interval_mode(struct rtr_socket *rtr_socket, enum rtr_interval_mode option) +{ + switch (option) { + case RTR_INTERVAL_MODE_IGNORE_ANY: + case RTR_INTERVAL_MODE_ACCEPT_ANY: + case RTR_INTERVAL_MODE_DEFAULT_MIN_MAX: + case RTR_INTERVAL_MODE_IGNORE_ON_FAILURE: + rtr_socket->iv_mode = option; + break; + default: + RTR_DBG1("Invalid interval mode. Mode remains unchanged."); + } +} diff --git a/rtrlib/rtr/rtr.h b/rtrlib/rtr/rtr.h new file mode 100644 index 0000000..162dadc --- /dev/null +++ b/rtrlib/rtr/rtr.h @@ -0,0 +1,161 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_rtr_h RTR socket + * @brief An RTR socket implements the RPKI-RTR protocol scheme. + * @details One rtr_socket communicates with a single RPKI-RTR server. + * @{ + */ + +#ifndef RTR_H +#define RTR_H +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +enum rtr_rtvals { RTR_SUCCESS = 0, RTR_ERROR = -1, RTR_INVALID_PARAM = -2 }; + +/** + * @brief These modes let the user configure how received intervals should be handled. + */ +enum rtr_interval_mode { + /** Ignore appliance of interval values at all. */ + RTR_INTERVAL_MODE_IGNORE_ANY, + + /** Accept any interval values, even if outside of range. */ + RTR_INTERVAL_MODE_ACCEPT_ANY, + + /** If interval value is outside of range, apply min (if below range) or max (if above range). */ + RTR_INTERVAL_MODE_DEFAULT_MIN_MAX, + + /** Ignore any interval values that are outside of range. */ + RTR_INTERVAL_MODE_IGNORE_ON_FAILURE +}; + +/** + * @brief States of the RTR socket. + */ +enum rtr_socket_state { + /** Socket is establishing the transport connection. */ + RTR_CONNECTING, + + /** Connection is established, + * socket is waiting for a Serial Notify or expiration of the refresh_interval timer + */ + RTR_ESTABLISHED, + + /** Resetting RTR connection. */ + RTR_RESET, + + /** Receiving validation records from the RTR server. */ + RTR_SYNC, + + /** Reconnect without any waiting period */ + RTR_FAST_RECONNECT, + + /** No validation records are available on the RTR server. */ + RTR_ERROR_NO_DATA_AVAIL, + + /** Server was unable to answer the last serial or reset query. */ + RTR_ERROR_NO_INCR_UPDATE_AVAIL, + + /** Fatal protocol error occurred. */ + RTR_ERROR_FATAL, + + /** Error on the transport socket occurred. */ + RTR_ERROR_TRANSPORT, + + /** RTR Socket was started, but now has shut down. */ + RTR_SHUTDOWN, + + /** RTR Socket has not been started yet. Initial state after rtr_init */ + RTR_CLOSED, +}; + +struct rtr_socket; + +/** + * @brief A function pointer that is called if the state of the rtr socket has changed. + */ +typedef void (*rtr_connection_state_fp)(const struct rtr_socket *rtr_socket, const enum rtr_socket_state state, + void *connection_state_fp_param_config, void *connection_state_fp_param_group); + +/** + * @brief A RTR socket. + * @param tr_socket Pointer to an initialized tr_socket that will be used to communicate with the RTR server. + * @param refresh_interval Time period in seconds. Tells the router how long to wait before next attempting + * to poll the cache, using a Serial Query or Reset Query PDU. + * @param last_update Timestamp of the last validation record update. Is 0 if the pfx_table doesn't store any + * validation records from this rtr_socket. + * @param expire_interval Time period in seconds. Received records are deleted if the client was unable to refresh data + * for this time period. If 0 is specified, the expire_interval is twice the refresh_interval. + * @param retry_interval Time period in seconds between a failed query and the next attempt. + * @param iv_mode Defines handling of incoming intervals. + * @param state Current state of the socket. + * @param session_id session_id of the RTR session. + * @param request_session_id True, if the rtr_client have to request a new none from the server. + * @param serial_number Last serial number of the obtained validation records. + * @param pfx_table pfx_table that stores the validation records obtained from the connected rtr server. + * @param thread_id Handle of the thread this socket is running in. + * @param connection_state_fp A callback function that is executed when the state of the socket changes. + * @param connection_state_fp_param_config Parameter that is passed to the connection_state_fp callback. + * Expects a pointer to a rtr_mgr_config struct. + * @param connection_state_fp_param_group Parameter that is passed to the connection_state_fp callback. + * Expects a pointer to the rtr_mgr_group this socket belongs to. + * @param version Protocol version used by this socket + * @param has_received_pdus True, if this socket has already received PDUs + * @param spki_table spki_table that stores the router keys obtained from the connected rtr server + */ +struct rtr_socket { + struct tr_socket *tr_socket; + unsigned int refresh_interval; + time_t last_update; + unsigned int expire_interval; + unsigned int retry_interval; + enum rtr_interval_mode iv_mode; + enum rtr_socket_state state; + uint32_t session_id; + bool request_session_id; + uint32_t serial_number; + struct pfx_table *pfx_table; + pthread_t thread_id; + rtr_connection_state_fp connection_state_fp; + void *connection_state_fp_param_config; + void *connection_state_fp_param_group; + unsigned int version; + bool has_received_pdus; + struct spki_table *spki_table; + bool is_resetting; +}; + +/** + * @brief Converts a rtr_socket_state to a String. + * @param[in] state state to convert to a string + * @return NULL If state isn't a valid rtr_socket_state + * @return !=NULL The rtr_socket_state as String. + */ +const char *rtr_state_to_str(enum rtr_socket_state state); + +/** + * @brief Set the interval option to the desired one. It's either RTR_INTERVAL_MODE_IGNORE_ANY, + * RTR_INTERVAL_MODE_APPLY_ANY, RTR_INTERVAL_MODE_DEFAULT_MIN_MAX or RTR_INTERVAL_MODE_IGNORE_ON_FAILURE. + * @param[in] rtr_socket The target socket. + * @param[in] option The new interval option that should be applied. + */ +void rtr_set_interval_mode(struct rtr_socket *rtr_socket, enum rtr_interval_mode option); + +/** + * @brief Get the current interval mode. + * @param[in] rtr_socket The target socket. + * @return The value of the interval_option variable. + */ +enum rtr_interval_mode rtr_get_interval_mode(struct rtr_socket *rtr_socket); +#endif +/** @} */ diff --git a/rtrlib/rtr/rtr_private.h b/rtrlib/rtr/rtr_private.h new file mode 100644 index 0000000..3e49215 --- /dev/null +++ b/rtrlib/rtr/rtr_private.h @@ -0,0 +1,88 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTR_PRIVATE_H +#define RTR_PRIVATE_H +#include "rtrlib/rtr/rtr.h" + +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +#define RTR_DBG(fmt, ...) lrtr_dbg("RTR Socket: " fmt, ##__VA_ARGS__) +#define RTR_DBG1(a) lrtr_dbg("RTR Socket: " a) + +static const uint32_t RTR_EXPIRATION_MIN = 600; // ten minutes +static const uint32_t RTR_EXPIRATION_MAX = 172800; // two days +static const uint32_t RTR_EXPIRATION_DEFAULT = 7200; // two hours + +static const uint32_t RTR_REFRESH_MIN = 1; // one second +static const uint32_t RTR_REFRESH_MAX = 86400; // one day +static const uint32_t RTR_REFRESH_DEFAULT = 3600; // one hour + +static const uint32_t RTR_RETRY_MIN = 1; // one second +static const uint32_t RTR_RETRY_MAX = 7200; // two hours +static const uint32_t RTR_RETRY_DEFAULT = 600; // ten minutes + +static const uint8_t RTR_PROTOCOL_VERSION_0; // = 0 +static const uint8_t RTR_PROTOCOL_VERSION_1 = 1; + +static const uint8_t RTR_PROTOCOL_MIN_SUPPORTED_VERSION; // = 0 +static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 1; + +enum rtr_interval_range { RTR_BELOW_INTERVAL_RANGE = -1, RTR_INSIDE_INTERVAL_RANGE = 0, RTR_ABOVE_INTERVAL_RANGE = 1 }; + +enum rtr_interval_type { RTR_INTERVAL_TYPE_EXPIRATION, RTR_INTERVAL_TYPE_REFRESH, RTR_INTERVAL_TYPE_RETRY }; + +/** + * @brief Initializes a rtr_socket. + * @param[out] rtr_socket Pointer to the allocated rtr_socket that will be initialized. + * @param[in] tr_socket Pointer to a tr_socket that will be used for the transport connection. + * If NULL the tr_socket element of the rtr_socket won't be changed. + * @param[in] pfx_table pfx_table that stores the validation records obtained from the connected rtr server. + * @param[in] spki_table spki_table that stores the router keys obtained from the connected rtr server. + * @param[in] refresh_interval Interval in seconds between serial queries that are sent to the server. + * Must be >= 1 and <= 86400 (one day), recommended default is 3600s (one hour). + * @param[in] expire_interval Stored validation records will be deleted + * if cache was unable to refresh data for this period. + * The value should be twice the refresh_interval. The value must be >= 600 (ten minutes) and <= 172800 (two days). + * The recommended default is 7200s (two hours). + * @param[in] retry_interval This parameter tells the router how long to wait (in seconds) before retrying + * a failed Serial Query or Reset Query. The value must be >= 1s and <= 7200s (two hours). + * The recommended default is 600 seconds (ten minutes). + * @param[in] iv_mode The interval mode that controls how new interval values are applied. + * @param[in] fp A callback function that is executed when the state of the socket changes. + * @param[in] fp_data_config Parameter that is passed to the connection_state_fp callback. + * Expects rtr_mgr_config. + * @param[in] fp_data_group Parameter that is passed to the connection_state_fp callback. + * Expects rtr_mgr_group. + * @return RTR_INVALID_PARAM If the refresh_interval or the expire_interval is not valid. + * @return RTR_SUCCESS On success. + */ +int rtr_init(struct rtr_socket *rtr_socket, struct tr_socket *tr_socket, struct pfx_table *pfx_table, + struct spki_table *spki_table, const unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval, enum rtr_interval_mode iv_mode, rtr_connection_state_fp fp, + void *fp_data_config, void *fp_data_group); + +/** + * @brief Starts the RTR protocol state machine in a pthread. Connection to the rtr_server will be established and the + * pfx_records will be synced. + * @param[in] rtr_socket rtr_socket that will be used. + * @return RTR_ERROR On error. + * @return RTR_SUCCESS On success. + */ +int rtr_start(struct rtr_socket *rtr_socket); + +/** + * @brief Stops the RTR connection and terminate the transport connection. + * @param[in] rtr_socket rtr_socket that will be used. + */ +void rtr_stop(struct rtr_socket *rtr_socket); + +#endif diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c new file mode 100644 index 0000000..cfd1de6 --- /dev/null +++ b/rtrlib/rtr_mgr.c @@ -0,0 +1,675 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtr_mgr_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/rtr/rtr_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <arpa/inet.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define MGR_DBG(fmt, ...) lrtr_dbg("RTR_MGR: " fmt, ##__VA_ARGS__) +#define MGR_DBG1(a) lrtr_dbg("RTR_MGR: " a) + +static const char *const mgr_str_status[] = { + [RTR_MGR_CLOSED] = "RTR_MGR_CLOSED", + [RTR_MGR_CONNECTING] = "RTR_MGR_CONNECTING", + [RTR_MGR_ESTABLISHED] = "RTR_MGR_ESTABLISHED", + [RTR_MGR_ERROR] = "RTR_MGR_ERROR", +}; + +static int rtr_mgr_config_cmp(const void *a, const void *b); +static int rtr_mgr_config_cmp_tommy(const void *a, const void *b); +static bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group); +static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config, + void *data_group); + +static void set_status(const struct rtr_mgr_config *conf, struct rtr_mgr_group *group, enum rtr_mgr_status mgr_status, + const struct rtr_socket *rtr_sock) +{ + MGR_DBG("Group(%u) status changed to: %s", group->preference, rtr_mgr_status_to_str(mgr_status)); + + group->status = mgr_status; + if (conf->status_fp) + conf->status_fp(group, mgr_status, rtr_sock, conf->status_fp_data); +} + +static int rtr_mgr_start_sockets(struct rtr_mgr_group *group) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + if (rtr_start(group->sockets[i]) != 0) { + MGR_DBG1("rtr_mgr: Error starting rtr_socket pthread"); + return RTR_ERROR; + } + } + group->status = RTR_MGR_CONNECTING; + return RTR_SUCCESS; +} + +static int rtr_mgr_init_sockets(struct rtr_mgr_group *group, struct rtr_mgr_config *config, + const unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval, enum rtr_interval_mode iv_mode) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + enum rtr_rtvals err_code = rtr_init(group->sockets[i], NULL, config->pfx_table, config->spki_table, + refresh_interval, expire_interval, retry_interval, iv_mode, + rtr_mgr_cb, config, group); + if (err_code) + return err_code; + } + return RTR_SUCCESS; +} + +bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group) +{ + for (unsigned int i = 0; i < group->sockets_len; i++) { + enum rtr_socket_state state = group->sockets[i]->state; + + if ((group->sockets[i]->last_update == 0) || + ((state != RTR_ESTABLISHED) && (state != RTR_RESET) && (state != RTR_SYNC))) + return false; + } + return true; +} + +static void rtr_mgr_close_less_preferable_groups(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group->status != RTR_MGR_CLOSED) && (current_group != group) && + (current_group->preference > group->preference)) { + for (unsigned int j = 0; j < current_group->sockets_len; j++) + rtr_stop(current_group->sockets[j]); + set_status(config, current_group, RTR_MGR_CLOSED, sock); + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); +} + +static struct rtr_mgr_group *get_best_inactive_rtr_mgr_group(struct rtr_mgr_config *config, struct rtr_mgr_group *group) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group != group) && (current_group->status == RTR_MGR_CLOSED)) { + pthread_mutex_unlock(&config->mutex); + return current_group; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return NULL; +} + +static bool is_some_rtr_mgr_group_established(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + if (group_node->group->status == RTR_MGR_ESTABLISHED) { + pthread_mutex_unlock(&config->mutex); + return true; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return false; +} + +static inline void _rtr_mgr_cb_state_shutdown(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + bool all_down = true; + + for (unsigned int i = 0; i < group->sockets_len; i++) { + if (group->sockets[i]->state != RTR_SHUTDOWN) { + all_down = false; + break; + } + } + if (all_down) + set_status(config, group, RTR_MGR_CLOSED, sock); + else + set_status(config, group, group->status, sock); +} + +static inline void _rtr_mgr_cb_state_established(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + /* socket established a connection to the rtr_server */ + if (group->status == RTR_MGR_CONNECTING) { + /* + * if previous state was CONNECTING, check if all + * other sockets in the group also have a established + * connection, if yes change group state to ESTABLISHED + */ + if (rtr_mgr_config_status_is_synced(group)) { + set_status(config, group, RTR_MGR_ESTABLISHED, sock); + rtr_mgr_close_less_preferable_groups(sock, config, group); + } else { + set_status(config, group, RTR_MGR_CONNECTING, sock); + } + } else if (group->status == RTR_MGR_ERROR) { + /* if previous state was ERROR, only change state to + * ESTABLISHED if all other more preferable socket + * groups are also in ERROR or SHUTDOWN state + */ + bool all_error = true; + + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group *current_group = group_node->group; + + if ((current_group != group) && (current_group->status != RTR_MGR_ERROR) && + (current_group->status != RTR_MGR_CLOSED) && + (current_group->preference < group->preference)) { + all_error = false; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + + if (all_error && rtr_mgr_config_status_is_synced(group)) { + set_status(config, group, RTR_MGR_ESTABLISHED, sock); + rtr_mgr_close_less_preferable_groups(sock, config, group); + } else { + set_status(config, group, RTR_MGR_ERROR, sock); + } + } +} + +static inline void _rtr_mgr_cb_state_connecting(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + if (group->status == RTR_MGR_ERROR) + set_status(config, group, RTR_MGR_ERROR, sock); + else + set_status(config, group, RTR_MGR_CONNECTING, sock); +} + +static inline void _rtr_mgr_cb_state_error(const struct rtr_socket *sock, struct rtr_mgr_config *config, + struct rtr_mgr_group *group) +{ + set_status(config, group, RTR_MGR_ERROR, sock); + + if (!is_some_rtr_mgr_group_established(config)) { + struct rtr_mgr_group *next_group = get_best_inactive_rtr_mgr_group(config, group); + + if (next_group) + rtr_mgr_start_sockets(next_group); + else + MGR_DBG1("No other inactive groups found"); + } +} + +static void rtr_mgr_cb(const struct rtr_socket *sock, const enum rtr_socket_state state, void *data_config, + void *data_group) +{ + if (state == RTR_SHUTDOWN) + MGR_DBG1("Received RTR_SHUTDOWN callback"); + + struct rtr_mgr_config *config = data_config; + struct rtr_mgr_group *group = data_group; + + if (!group) { + MGR_DBG1("ERROR: Socket has no group"); + return; + } + + switch (state) { + case RTR_SHUTDOWN: + _rtr_mgr_cb_state_shutdown(sock, config, group); + break; + case RTR_ESTABLISHED: + _rtr_mgr_cb_state_established(sock, config, group); + break; + case RTR_CONNECTING: + _rtr_mgr_cb_state_connecting(sock, config, group); + break; + case RTR_ERROR_FATAL: + case RTR_ERROR_TRANSPORT: + case RTR_ERROR_NO_DATA_AVAIL: + _rtr_mgr_cb_state_error(sock, config, group); + break; + default: + set_status(config, group, group->status, sock); + } +} + +int rtr_mgr_config_cmp(const void *a, const void *b) +{ + const struct rtr_mgr_group *ar = a; + const struct rtr_mgr_group *br = b; + + if (ar->preference > br->preference) + return 1; + else if (ar->preference < br->preference) + return -1; + return 0; +} + +int rtr_mgr_config_cmp_tommy(const void *a, const void *b) +{ + const struct rtr_mgr_group_node *ar = a; + const struct rtr_mgr_group_node *br = b; + + return rtr_mgr_config_cmp(ar->group, br->group); +} + +RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[], + const unsigned int groups_len, const unsigned int refresh_interval, + const unsigned int expire_interval, const unsigned int retry_interval, + const pfx_update_fp update_fp, const spki_update_fp spki_update_fp, + const rtr_mgr_status_fp status_fp, void *status_fp_data) +{ + enum rtr_rtvals err_code = RTR_ERROR; + enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; + struct pfx_table *pfxt = NULL; + struct spki_table *spki_table = NULL; + struct rtr_mgr_config *config = NULL; + struct rtr_mgr_group *cg = NULL; + struct rtr_mgr_group_node *group_node; + uint8_t last_preference = UINT8_MAX; + + *config_out = NULL; + + if (groups_len == 0) { + MGR_DBG1("Error Empty rtr_mgr_group array"); + return RTR_ERROR; + } + + *config_out = config = lrtr_malloc(sizeof(*config)); + if (!config) + return RTR_ERROR; + + config->len = groups_len; + + if (pthread_mutex_init(&config->mutex, NULL) != 0) { + MGR_DBG1("Mutex initialization failed"); + goto err; + } + /* sort array in asc order, so we can check for dupl. pref */ + qsort(groups, groups_len, sizeof(struct rtr_mgr_group), &rtr_mgr_config_cmp); + + /* Check that the groups have unique pref and at least one socket */ + for (unsigned int i = 0; i < config->len; i++) { + if ((i > 0) && (groups[i].preference == last_preference)) { + MGR_DBG1("Error Same preference for 2 socket groups!"); + goto err; + } + if (groups[i].sockets_len == 0) { + MGR_DBG1("Error Empty sockets array in socket group!"); + goto err; + } + + last_preference = groups[i].preference; + } + + /* Init data structures that we need to pass to the sockets */ + pfxt = lrtr_malloc(sizeof(*pfxt)); + if (!pfxt) + goto err; + pfx_table_init(pfxt, update_fp); + + spki_table = lrtr_malloc(sizeof(*spki_table)); + if (!spki_table) + goto err; + spki_table_init(spki_table, spki_update_fp); + + config->pfx_table = pfxt; + config->spki_table = spki_table; + + /* Copy the groups from the array into linked list config->groups */ + config->len = groups_len; + config->groups = lrtr_malloc(sizeof(*config->groups)); + if (!config->groups) + goto err; + config->groups->list = NULL; + + for (unsigned int i = 0; i < groups_len; i++) { + cg = lrtr_malloc(sizeof(struct rtr_mgr_group)); + if (!cg) + goto err; + + memcpy(cg, &groups[i], sizeof(struct rtr_mgr_group)); + + cg->status = RTR_MGR_CLOSED; + err_code = rtr_mgr_init_sockets(cg, config, refresh_interval, expire_interval, retry_interval, iv_mode); + if (err_code) + goto err; + + group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node)); + if (!group_node) + goto err; + + group_node->group = cg; + tommy_list_insert_tail(&config->groups->list, &group_node->node, group_node); + } + /* Our linked list should be sorted already, since the groups array was + * sorted. However, for safety reasons we sort again. + */ + tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy); + + config->status_fp_data = status_fp_data; + config->status_fp = status_fp; + return RTR_SUCCESS; + +err: + if (spki_table) + spki_table_free(spki_table); + if (pfxt) + pfx_table_free(pfxt); + lrtr_free(pfxt); + lrtr_free(spki_table); + + lrtr_free(cg); + + lrtr_free(config->groups); + lrtr_free(config); + config = NULL; + *config_out = NULL; + + return err_code; +} + +RTRLIB_EXPORT struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config) +{ + tommy_node *head = tommy_list_head(&config->groups->list); + struct rtr_mgr_group_node *group_node = head->data; + + return group_node->group; +} + +RTRLIB_EXPORT int rtr_mgr_start(struct rtr_mgr_config *config) +{ + MGR_DBG("%s()", __func__); + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + return rtr_mgr_start_sockets(best_group); +} + +RTRLIB_EXPORT bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + bool all_sync = true; + struct rtr_mgr_group_node *group_node = node->data; + + for (unsigned int j = 0; all_sync && (j < group_node->group->sockets_len); j++) { + if (group_node->group->sockets[j]->last_update == 0) + all_sync = false; + } + if (all_sync) { + pthread_mutex_unlock(&config->mutex); + return true; + } + node = node->next; + } + pthread_mutex_unlock(&config->mutex); + return false; +} + +RTRLIB_EXPORT void rtr_mgr_free(struct rtr_mgr_config *config) +{ + MGR_DBG("%s()", __func__); + pthread_mutex_lock(&config->mutex); + + pfx_table_free(config->pfx_table); + spki_table_free(config->spki_table); + lrtr_free(config->spki_table); + lrtr_free(config->pfx_table); + + /* Free linked list */ + tommy_node *head = tommy_list_head(&config->groups->list); + + while (head) { + tommy_node *tmp = head; + struct rtr_mgr_group_node *group_node = tmp->data; + + head = head->next; + for (unsigned int j = 0; j < group_node->group->sockets_len; j++) + tr_free(group_node->group->sockets[j]->tr_socket); + + lrtr_free(group_node->group); + lrtr_free(group_node); + } + + lrtr_free(config->groups); + + pthread_mutex_unlock(&config->mutex); + pthread_mutex_destroy(&config->mutex); + lrtr_free(config); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline int rtr_mgr_validate(struct rtr_mgr_config *config, const uint32_t asn, + const struct lrtr_ip_addr *prefix, const uint8_t mask_len, + enum pfxv_state *result) +{ + return pfx_table_validate(config->pfx_table, asn, prefix, mask_len, result); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline int rtr_mgr_get_spki(struct rtr_mgr_config *config, const uint32_t asn, uint8_t *ski, + struct spki_record **result, unsigned int *result_count) +{ + return spki_table_get_all(config->spki_table, asn, ski, result, result_count); +} + +RTRLIB_EXPORT void rtr_mgr_stop(struct rtr_mgr_config *config) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *node = tommy_list_head(&config->groups->list); + + MGR_DBG("%s()", __func__); + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + for (unsigned int j = 0; j < group_node->group->sockets_len; j++) + rtr_stop(group_node->group->sockets[j]); + node = node->next; + } + pthread_mutex_unlock(&config->mutex); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct rtr_mgr_group *group) +{ + unsigned int refresh_iv = 3600; + unsigned int retry_iv = 600; + unsigned int expire_iv = 7200; + enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; + enum rtr_rtvals err_code = RTR_ERROR; + struct rtr_mgr_group_node *new_group_node = NULL; + struct rtr_mgr_group *new_group = NULL; + struct rtr_mgr_group_node *gnode; + + pthread_mutex_lock(&config->mutex); + + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + gnode = node->data; + if (gnode->group->preference == group->preference) { + MGR_DBG1("Group with preference value already exists!"); + err_code = RTR_INVALID_PARAM; + goto err; + } + + // TODO This is not pretty. It wants to get the same intervals + // that are being used by other groups. Maybe intervals should + // be store globally/per-group/per-socket? + if (gnode->group->sockets[0]->refresh_interval) + refresh_iv = gnode->group->sockets[0]->refresh_interval; + if (gnode->group->sockets[0]->retry_interval) + retry_iv = gnode->group->sockets[0]->retry_interval; + if (gnode->group->sockets[0]->expire_interval) + expire_iv = gnode->group->sockets[0]->expire_interval; + node = node->next; + } + new_group = lrtr_malloc(sizeof(struct rtr_mgr_group)); + + if (!new_group) + goto err; + + memcpy(new_group, group, sizeof(struct rtr_mgr_group)); + new_group->status = RTR_MGR_CLOSED; + + err_code = rtr_mgr_init_sockets(new_group, config, refresh_iv, expire_iv, retry_iv, iv_mode); + if (err_code) + goto err; + + new_group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node)); + if (!new_group_node) + goto err; + + new_group_node->group = new_group; + tommy_list_insert_tail(&config->groups->list, &new_group_node->node, new_group_node); + config->len++; + + MGR_DBG("Group with preference %d successfully added!", new_group->preference); + + tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp_tommy); + + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + if (best_group->status == RTR_MGR_CLOSED) + rtr_mgr_start_sockets(best_group); + + pthread_mutex_unlock(&config->mutex); + return RTR_SUCCESS; + +err: + pthread_mutex_unlock(&config->mutex); + + if (new_group) + lrtr_free(new_group); + + return err_code; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference) +{ + pthread_mutex_lock(&config->mutex); + tommy_node *remove_node = NULL; + tommy_node *node = tommy_list_head(&config->groups->list); + struct rtr_mgr_group_node *group_node; + struct rtr_mgr_group *remove_group; + + if (config->len == 1) { + MGR_DBG1("Cannot remove last remaining group!"); + pthread_mutex_unlock(&config->mutex); + return RTR_ERROR; + } + + // Find the node of the group we want to remove + while (node && !remove_node) { + group_node = node->data; + if (group_node->group->preference == preference) + remove_node = node; + node = node->next; + } + + if (!remove_node) { + MGR_DBG1("The group that should be removed does not exist!"); + pthread_mutex_unlock(&config->mutex); + return RTR_ERROR; + } + + group_node = remove_node->data; + remove_group = group_node->group; + tommy_list_remove_existing(&config->groups->list, remove_node); + config->len--; + MGR_DBG("Group with preference %d successfully removed!", preference); + // tommy_list_sort(&config->groups->list, &rtr_mgr_config_cmp); + pthread_mutex_unlock(&config->mutex); + + // If group isn't closed, make it so! + if (remove_group->status != RTR_MGR_CLOSED) { + for (unsigned int j = 0; j < remove_group->sockets_len; j++) { + rtr_stop(remove_group->sockets[j]); + tr_free(remove_group->sockets[j]->tr_socket); + } + set_status(config, remove_group, RTR_MGR_CLOSED, NULL); + } + + struct rtr_mgr_group *best_group = rtr_mgr_get_first_group(config); + + if (best_group->status == RTR_MGR_CLOSED) + rtr_mgr_start_sockets(best_group); + + lrtr_free(group_node->group); + lrtr_free(group_node); + return RTR_SUCCESS; +} + +// TODO: write test for this function. +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_for_each_group(struct rtr_mgr_config *config, + void(fp)(const struct rtr_mgr_group *group, void *data), void *data) +{ + tommy_node *node = tommy_list_head(&config->groups->list); + + while (node) { + struct rtr_mgr_group_node *group_node = node->data; + + fp(group_node->group, data); + node = node->next; + } + + return RTR_SUCCESS; +} + +RTRLIB_EXPORT const char *rtr_mgr_status_to_str(enum rtr_mgr_status status) +{ + return mgr_str_status[status]; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv4_record(struct rtr_mgr_config *config, + void(fp)(const struct pfx_record *, void *data), void *data) +{ + pfx_table_for_each_ipv4_record(config->pfx_table, fp, data); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *config, + void(fp)(const struct pfx_record *, void *data), void *data) +{ + pfx_table_for_each_ipv6_record(config->pfx_table, fp, data); +} diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h new file mode 100644 index 0000000..b267723 --- /dev/null +++ b/rtrlib/rtr_mgr.h @@ -0,0 +1,261 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_rtr_mgr_h RTR connection manager + * @brief The RTR connection manager maintains multiple groups of @ref + * rtr_socket "RTR sockets". + * @details The RTR connection manager is initialized with one or multiple + * groups of rtr_sockets. Each group is configured with a preference + * value and contains a set of rtr_socket RTR sockets. It connects to all + * sockets of the group with the lowest preference value.\n + * In case of failures, the connection manager establishes connections to + * RTR servers of another group with the next lowest preference value (see + * <a href="https://tools.ietf.org/html/rfc6810">IETF + * RFC 6810</a> for details about error handling).\n + * RTRlib also supports a Retry Interval (see + * <a href="https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis"> + * draft-ietf-sidr-rpki-rtr-rfc6810-bis</a>). + * If a more preferred group is online again, the RTR connection manager + * will switch back and close connections to the caches of the less + * preferred group. + * + * @{ + * @example rtr_mgr.c + * Usage example of the RTR connection manager. + */ +#ifndef RTR_MGR +#define RTR_MGR + +#include "rtrlib/pfx/pfx.h" +#include "rtrlib/spki/spkitable.h" + +#include <pthread.h> +#include <stdint.h> + +/** + * @brief Status of a rtr_mgr_group. + */ +enum rtr_mgr_status { + /** RTR sockets are disconnected */ + RTR_MGR_CLOSED, + /** RTR sockets trying to establish a connection. */ + RTR_MGR_CONNECTING, + /** All RTR sockets of the group are synchronized with rtr servers. */ + RTR_MGR_ESTABLISHED, + /** Error occurred on at least one RTR socket. */ + RTR_MGR_ERROR, +}; + +/** + * @brief A set of RTR sockets. + * @param sockets Array of rtr_socket pointer. The tr_socket element of + * the rtr_socket must be associated with an initialized # + * transport socket. + * @param sockets_len Number of elements in the sockets array. + * @param preference The preference value of this group. + * Groups with lower preference values are preferred. + * @param status Status of the group. + */ +struct rtr_mgr_group { + struct rtr_socket **sockets; + unsigned int sockets_len; + uint8_t preference; + enum rtr_mgr_status status; +}; + +typedef void (*rtr_mgr_status_fp)(const struct rtr_mgr_group *, enum rtr_mgr_status, const struct rtr_socket *, void *); + +struct tommy_list_wrapper; + +// TODO Add refresh, expire, and retry intervals to config for easier access. +struct rtr_mgr_config { + struct tommy_list_wrapper *groups; + unsigned int len; + pthread_mutex_t mutex; + rtr_mgr_status_fp status_fp; + void *status_fp_data; + struct pfx_table *pfx_table; + struct spki_table *spki_table; +}; + +/** + * @brief Initializes a rtr_mgr_config. + * @param[out] config_out The rtr_mgr_config that will be initialized by this + * function. On error, *config_out will be NULL! + * @param[in] groups Linked list of rtr_mgr_group. Every RTR socket in an + * rtr_mgr_group must be assoziated with an initialized + * transport socket. A Transport socket is only allowed to be + * associated with one rtr socket. The preference values must + * be unique in the linked list. More than one rtr_mgr_group + * with the same preference value isn't allowed. + * @param[in] groups_len Number of elements in the groups array. Must be >= 1. + * @param[in] refresh_interval Interval in seconds between serial queries that + * are sent to the server. Must be >= 1 and <= + * 86400s (1d), recommended default is 3600s (1h). + * @param[in] expire_interval Stored validation records will be deleted if + * cache was unable to refresh data for this period. + * The value should be twice the refresh_interval + * and must be >= 600s (10min) and <= 172800s (2d). + * The recommended default is 7200s (2h). + * @param[in] retry_interval This parameter tells the router how long to wait + * (in seconds) before retrying a failed Serial Query + * or Reset Query. + * The value must be >= 1s and <= 7200s (2h). + * The recommended default is 600s (10min). + * @param[in] update_fp Pointer to pfx_update_fp callback, that is executed for + every added and removed pfx_record. + * @param[in] spki_update_fp Pointer to spki_update_fp callback, that is + executed for every added and removed spki_record. + * @param[in] status_fp Pointer to a function that is called if the connection + * status from one of the socket groups is changed. + * @param[in] status_fp_data Pointer to a memory area that is passed to the + * status_fp function. Memory area can be freely used + * to pass user-defined data to the status_fp + * callback. + * @return RTR_ERROR If an error occurred + * @return RTR_INVALID_PARAM If refresh_interval or expire_interval is invalid. + * @return RTR_SUCCESS On success. + */ +int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[], const unsigned int groups_len, + const unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval, const pfx_update_fp update_fp, const spki_update_fp spki_update_fp, + const rtr_mgr_status_fp status_fp, void *status_fp_data); + +/** + * @brief Adds a new rtr_mgr_group to the linked list of a initialized config. + * @details A new group must have at least one rtr_socket associated + * with it. This socket must have at least one initialized + * transport socket associated with it. The new group must + * have a preference value that is none of the already present + * groups have. More than one rtr_mgr_group with the same + * preference is not allowed. + * @param config A rtr_mgr_config struct that has been initialized + * previously with rtr_mgr_init + * @param group A rtr_mgr_group with at least one rtr_socket and a + * preference value that no existing group has. + * @return RTR_INVALID_PARAM If a group with the same preference value already + * exists. + * @return RTR_ERROR If an error occurred while adding the group. + * @return RTR_SUCCESS If the group was successfully added. + * + */ +int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct rtr_mgr_group *group); +/** + * @brief Removes an existing rtr_mgr_group from the linked list of config. + * @details The group to be removed is identified by its preference value. + * Should the group to be removed be currently active, it will be + * shut down and the next best group will be spun up. + * @param config A rtr_mgr_config struct that has been initialized previously + * with rtr_mgr_init + * @param preference The preference value of the group to be removed. + * @return RTR_ERROR If no group with this preference value exists. + * @return RTR_SUCCESS If group was successfully removed. + * + */ +int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference); +/** + * @brief Frees all resources that were allocated from the rtr_mgr. + * @details rtr_mgr_stop must be called before, to shutdown all rtr_sockets. + * @param[in] config rtr_mgr_config. + */ +void rtr_mgr_free(struct rtr_mgr_config *config); + +/** + * @brief Establishes rtr_socket connections + * @details Establishes the connection with the rtr_sockets of the group + * with the lowest preference value and handles errors as defined in the + * RPKI-RTR protocol. + * @param[in] config Pointer to an initialized rtr_mgr_config. + * @return RTR_SUCCESS On success + * @return RTR_ERROR On error + */ +int rtr_mgr_start(struct rtr_mgr_config *config); + +/** + * @brief Terminates rtr_socket connections + * @details Terminates all rtr_socket connections defined in the config. + * All pfx_records received from these sockets will be purged. + * @param[in] config The rtr_mgr_config struct + */ +void rtr_mgr_stop(struct rtr_mgr_config *config); + +/** + * @brief Check if rtr_mgr_group is fully synchronized with at least one group. + * @param[in] config The rtr_mgr_config. + * @return true If pfx_table stores non-outdated pfx_records + * @return false If pfx_table isn't fully synchronized with at least one group. + */ +bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config); + +/** + * @brief Validates the origin of a BGP-Route. + * @param[in] config The rtr_mgr_config + * @param[in] asn Autonomous system number of the Origin-AS of the prefix + * @param[in] prefix Announced network prefix + * @param[in] mask_len Length of the network mask of the announced prefix + * @param[out] result Outcome of the validation + * @return PFX_SUCCESS On success. + * @return PFX_ERROR If an error occurred. + */ +int rtr_mgr_validate(struct rtr_mgr_config *config, const uint32_t asn, const struct lrtr_ip_addr *prefix, + const uint8_t mask_len, enum pfxv_state *result); + +/** + * @brief Returns all SPKI records which match the given ASN and SKI. + * @param[in] config + * @param[in] asn Autonomous system number of the Origin-AS + * @param[in] ski the SKI to search for + * @param[out] result a array of all matching spki_records + * @param[out] result_count number of returned spki_records + * @return SPKI_SUCCESS On success + * @return SPKI_ERROR If an error occurred + */ +int rtr_mgr_get_spki(struct rtr_mgr_config *config, const uint32_t asn, uint8_t *ski, struct spki_record **result, + unsigned int *result_count); + +/** + * @brief Converts a rtr_mgr_status to a String. + * @param[in] status state to convert to a string. + * @return NULL If status isn't a valid rtr_mgr_status. + * @return !=NULL The rtr_rtr_mgr_status as String. + */ +const char *rtr_mgr_status_to_str(enum rtr_mgr_status status); + +/** + * @brief Iterates over all IPv4 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] config rtr_mgr_config + * @param[in] fp Pointer to callback function with signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void rtr_mgr_for_each_ipv4_record(struct rtr_mgr_config *config, pfx_for_each_fp fp, void *data); + +/** + * @brief Iterates over all IPv6 records in the pfx_table. + * @details For every pfx_record the function fp is called. The pfx_record and + * the data pointer is passed to the fp. + * @param[in] config rtr_mgr_config + * @param[in] fp Pointer to callback function with signature \c pfx_for_each_fp. + * @param[in] data This parameter is forwarded to the callback function. + */ +void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *config, pfx_for_each_fp fp, void *data); + +/** + * @brief Returns the first, thus active group. + * @param[in] config The rtr_mgr_config + * @return rtr_mgr_group The head of the linked list. + */ +struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config); + +int rtr_mgr_for_each_group(struct rtr_mgr_config *config, void (*fp)(const struct rtr_mgr_group *group, void *data), + void *data); +#endif +/** @} */ diff --git a/rtrlib/rtr_mgr_private.h b/rtrlib/rtr_mgr_private.h new file mode 100644 index 0000000..d654820 --- /dev/null +++ b/rtrlib/rtr_mgr_private.h @@ -0,0 +1,27 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTR_MGR_PRIVATE +#define RTR_MGR_PRIVATE + +#include "rtrlib/rtr_mgr.h" + +#include "third-party/tommyds/tommylist.h" + +struct tommy_list_wrapper { + tommy_list list; +}; + +// TODO Find a nicer way todo a linked list (without writing our own) +struct rtr_mgr_group_node { + tommy_node node; + struct rtr_mgr_group *group; +}; + +#endif diff --git a/rtrlib/rtrlib.h.cmake b/rtrlib/rtrlib.h.cmake new file mode 100644 index 0000000..de8f612 --- /dev/null +++ b/rtrlib/rtrlib.h.cmake @@ -0,0 +1,40 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef RTRLIB_H +#define RTRLIB_H + +#cmakedefine RTRLIB_HAVE_LIBSSH +#define RTRLIB_VERSION_MAJOR @RTRLIB_VERSION_MAJOR@ +#define RTRLIB_VERSION_MINOR @RTRLIB_VERSION_MINOR@ +#define RTRLIB_VERSION_PATCH @RTRLIB_VERSION_PATCH@ + +#include "lib/alloc_utils.h" +#include "lib/ip.h" +#include "lib/ipv4.h" +#include "lib/ipv6.h" +#include "pfx/pfx.h" +#include "rtr/rtr.h" +#include "rtr_mgr.h" +#include "spki/spkitable.h" +#include "transport/tcp/tcp_transport.h" +#include "transport/transport.h" +#ifdef RTRLIB_HAVE_LIBSSH +#include "rtrlib/transport/ssh/ssh_transport.h" +#endif + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/rtrlib/rtrlib_export_private.h b/rtrlib/rtrlib_export_private.h new file mode 100644 index 0000000..c5ed4b3 --- /dev/null +++ b/rtrlib/rtrlib_export_private.h @@ -0,0 +1,16 @@ + +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef RTRLIB_EXPORT_H +#define RTRLIB_EXPORT_H + +#define RTRLIB_EXPORT __attribute__((visibility("default"))) + +#endif diff --git a/rtrlib/spki/hashtable/ht-spkitable.c b/rtrlib/spki/hashtable/ht-spkitable.c new file mode 100644 index 0000000..047ab3a --- /dev/null +++ b/rtrlib/spki/hashtable/ht-spkitable.c @@ -0,0 +1,368 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ht-spkitable_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" + +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +struct key_entry { + uint8_t ski[SKI_SIZE]; + uint32_t asn; + uint8_t spki[SPKI_SIZE]; + const struct rtr_socket *socket; + tommy_node hash_node; + tommy_node list_node; +}; + +/** + * @brief Compares two key_entries by comparing ASN, SKI and SPKI + * @param[in] arg Pointer to first key_entry + * @param[in] obj Pointer to second key_entry + * @return 1 if not equal + * @return 0 if equal + */ +static int key_entry_cmp(const void *arg, const void *obj) +{ + const struct key_entry *param = arg; + const struct key_entry *entry = obj; + + if (param->asn != entry->asn) + return 1; + if (memcmp(param->ski, entry->ski, sizeof(entry->ski))) + return 1; + if (memcmp(param->spki, entry->spki, sizeof(entry->spki))) + return 1; + if (param->socket != entry->socket) + return 1; + + return 0; +} + +/* Copying the content, target struct must already be allocated */ +static void key_entry_to_spki_record(struct key_entry *key_e, struct spki_record *spki_r) +{ + spki_r->asn = key_e->asn; + spki_r->socket = key_e->socket; + memcpy(spki_r->ski, key_e->ski, sizeof(key_e->ski)); + memcpy(spki_r->spki, key_e->spki, sizeof(key_e->spki)); +} + +/* Copying the content, target struct must already be allocated */ +static void spki_record_to_key_entry(struct spki_record *spki_r, struct key_entry *key_e) +{ + key_e->asn = spki_r->asn; + key_e->socket = spki_r->socket; + memcpy(key_e->ski, spki_r->ski, sizeof(spki_r->ski)); + memcpy(key_e->spki, spki_r->spki, sizeof(spki_r->spki)); +} + +/** + * @brief Calls the spki_table update function. + * @param[in] spki_table spki_table to use. + * @param[in] record spki_record that was added/removed. + * @param[in] added True means record was added, False means removed + */ +static void spki_table_notify_clients(struct spki_table *spki_table, const struct spki_record *record, const bool added) +{ + if (spki_table->update_fp) + spki_table->update_fp(spki_table, *record, added); +} + +void spki_table_init(struct spki_table *spki_table, spki_update_fp update_fp) +{ + tommy_hashlin_init(&spki_table->hashtable); + tommy_list_init(&spki_table->list); + pthread_rwlock_init(&spki_table->lock, NULL); + spki_table->cmp_fp = key_entry_cmp; + spki_table->update_fp = update_fp; +} + +void spki_table_free(struct spki_table *spki_table) +{ + pthread_rwlock_wrlock(&spki_table->lock); + + tommy_list_foreach(&spki_table->list, free); + tommy_hashlin_done(&spki_table->hashtable); + + pthread_rwlock_unlock(&spki_table->lock); + pthread_rwlock_destroy(&spki_table->lock); +} + +void spki_table_free_without_notify(struct spki_table *spki_table) +{ + pthread_rwlock_wrlock(&spki_table->lock); + + spki_table->update_fp = NULL; + tommy_list_foreach(&spki_table->list, free); + tommy_hashlin_done(&spki_table->hashtable); + + pthread_rwlock_unlock(&spki_table->lock); + pthread_rwlock_destroy(&spki_table->lock); +} + +int spki_table_add_entry(struct spki_table *spki_table, struct spki_record *spki_record) +{ + uint32_t hash; + struct key_entry *entry; + + entry = lrtr_malloc(sizeof(*entry)); + if (!entry) + return SPKI_ERROR; + + spki_record_to_key_entry(spki_record, entry); + hash = tommy_inthash_u32(spki_record->asn); + + pthread_rwlock_wrlock(&spki_table->lock); + if (tommy_hashlin_search(&spki_table->hashtable, spki_table->cmp_fp, entry, hash)) { + lrtr_free(entry); + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_DUPLICATE_RECORD; + } + + /* Insert into hashtable and list */ + tommy_hashlin_insert(&spki_table->hashtable, &entry->hash_node, entry, hash); + tommy_list_insert_tail(&spki_table->list, &entry->list_node, entry); + pthread_rwlock_unlock(&spki_table->lock); + spki_table_notify_clients(spki_table, spki_record, true); + return SPKI_SUCCESS; +} + +int spki_table_get_all(struct spki_table *spki_table, uint32_t asn, uint8_t *ski, struct spki_record **result, + unsigned int *result_size) +{ + uint32_t hash = tommy_inthash_u32(asn); + tommy_node *result_bucket; + void *tmp; + + *result = NULL; + *result_size = 0; + + pthread_rwlock_rdlock(&spki_table->lock); + + /** + * A tommy node contains its storing key_entry (->data) as well as + * next and prev pointer to accommodate multiple results. + * The bucket is guaranteed to contain ALL the elements + * with the specified hash, but it can contain also others. + */ + result_bucket = tommy_hashlin_bucket(&spki_table->hashtable, hash); + + if (!result_bucket) { + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_SUCCESS; + } + + /* Build the result array */ + while (result_bucket) { + struct key_entry *element; + + element = result_bucket->data; + if (element->asn == asn && memcmp(element->ski, ski, sizeof(element->ski)) == 0) { + (*result_size)++; + tmp = lrtr_realloc(*result, *result_size * sizeof(**result)); + if (!tmp) { + lrtr_free(*result); + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_ERROR; + } + *result = tmp; + key_entry_to_spki_record(element, *result + *result_size - 1); + } + result_bucket = result_bucket->next; + } + + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_SUCCESS; +} + +// cppcheck-suppress unusedFunction +int spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result, + unsigned int *result_size) +{ + tommy_node *current_node; + void *tmp; + *result = NULL; + *result_size = 0; + + pthread_rwlock_rdlock(&spki_table->lock); + + current_node = tommy_list_head(&spki_table->list); + while (current_node) { + struct key_entry *current_entry; + + current_entry = (struct key_entry *)current_node->data; + + if (memcmp(current_entry->ski, ski, sizeof(current_entry->ski)) == 0) { + (*result_size)++; + tmp = lrtr_realloc(*result, sizeof(**result) * (*result_size)); + if (!tmp) { + lrtr_free(*result); + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_ERROR; + } + *result = tmp; + key_entry_to_spki_record(current_entry, *result + (*result_size - 1)); + } + current_node = current_node->next; + } + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_SUCCESS; +} + +int spki_table_remove_entry(struct spki_table *spki_table, struct spki_record *spki_record) +{ + uint32_t hash; + struct key_entry entry; + struct key_entry *rmv_elem; + int rtval = SPKI_ERROR; + + spki_record_to_key_entry(spki_record, &entry); + hash = tommy_inthash_u32(spki_record->asn); + + pthread_rwlock_wrlock(&spki_table->lock); + + if (!tommy_hashlin_search(&spki_table->hashtable, spki_table->cmp_fp, &entry, hash)) { + rtval = SPKI_RECORD_NOT_FOUND; + } else { + /* Remove from hashtable and list */ + rmv_elem = tommy_hashlin_remove(&spki_table->hashtable, spki_table->cmp_fp, &entry, hash); + if (rmv_elem && tommy_list_remove_existing(&spki_table->list, &rmv_elem->list_node)) { + lrtr_free(rmv_elem); + spki_table_notify_clients(spki_table, spki_record, false); + rtval = SPKI_SUCCESS; + } + } + pthread_rwlock_unlock(&spki_table->lock); + return rtval; +} + +int spki_table_src_remove(struct spki_table *spki_table, const struct rtr_socket *socket) +{ + struct key_entry *entry; + tommy_node *current_node; + + pthread_rwlock_wrlock(&spki_table->lock); + + current_node = tommy_list_head(&spki_table->list); + while (current_node) { + entry = current_node->data; + if (entry->socket == socket) { + current_node = current_node->next; + if (!tommy_list_remove_existing(&spki_table->list, &entry->list_node)) { + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_ERROR; + } + if (!tommy_hashlin_remove_existing(&spki_table->hashtable, &entry->hash_node)) { + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_ERROR; + } + lrtr_free(entry); + } else { + current_node = current_node->next; + } + } + pthread_rwlock_unlock(&spki_table->lock); + return SPKI_SUCCESS; +} + +int spki_table_copy_except_socket(struct spki_table *src, struct spki_table *dst, struct rtr_socket *socket) +{ + tommy_node *current_node; + int ret = SPKI_SUCCESS; + + pthread_rwlock_rdlock(&src->lock); + current_node = tommy_list_head(&src->list); + while (current_node) { + struct key_entry *entry; + struct spki_record record; + + entry = (struct key_entry *)current_node->data; + key_entry_to_spki_record(entry, &record); + + if (entry->socket != socket) { + if (spki_table_add_entry(dst, &record) != SPKI_SUCCESS) { + ret = SPKI_ERROR; + break; + } + } + current_node = current_node->next; + } + + pthread_rwlock_unlock(&src->lock); + + return ret; +} + +void spki_table_notify_diff(struct spki_table *new_table, struct spki_table *old_table, const struct rtr_socket *socket) +{ + spki_update_fp old_table_fp; + + // Disable update callback for old_table + old_table_fp = old_table->update_fp; + old_table->update_fp = NULL; + + // Iterate new_table and try to delete every entry from the given socket + // in old_table If the prefix could not be removed it was added in + // new_table and the update cb must be called + for (tommy_node *current_node = tommy_list_head(&new_table->list); current_node; + current_node = current_node->next) { + struct key_entry *entry = (struct key_entry *)current_node->data; + + if (entry->socket == socket) { + struct spki_record record; + + key_entry_to_spki_record(entry, &record); + + if (spki_table_remove_entry(old_table, &record) == SPKI_RECORD_NOT_FOUND) + spki_table_notify_clients(new_table, &record, true); + } + } + + // Iterate old_table and call cb for every remianing entry from the + // given socket with added false because it is not present in new_table + for (tommy_node *current_node = tommy_list_head(&old_table->list); current_node; + current_node = current_node->next) { + struct key_entry *entry = (struct key_entry *)current_node->data; + + if (entry->socket == socket) { + struct spki_record record; + + key_entry_to_spki_record(entry, &record); + spki_table_notify_clients(new_table, &record, false); + } + } + + // Restore original state of old_tables update_fp + old_table->update_fp = old_table_fp; +} + +void spki_table_swap(struct spki_table *a, struct spki_table *b) +{ + tommy_hashlin tmp_hashtable; + tommy_list tmp_list; + + pthread_rwlock_wrlock(&a->lock); + pthread_rwlock_wrlock(&b->lock); + + memcpy(&tmp_hashtable, &a->hashtable, sizeof(tmp_hashtable)); + memcpy(&tmp_list, &a->list, sizeof(tmp_list)); + + memcpy(&a->hashtable, &b->hashtable, sizeof(tmp_hashtable)); + memcpy(&a->list, &b->list, sizeof(tmp_list)); + + memcpy(&b->hashtable, &tmp_hashtable, sizeof(tmp_hashtable)); + memcpy(&b->list, &tmp_list, sizeof(tmp_list)); + + pthread_rwlock_unlock(&a->lock); + pthread_rwlock_unlock(&b->lock); +} diff --git a/rtrlib/spki/hashtable/ht-spkitable_private.h b/rtrlib/spki/hashtable/ht-spkitable_private.h new file mode 100644 index 0000000..05b0a48 --- /dev/null +++ b/rtrlib/spki/hashtable/ht-spkitable_private.h @@ -0,0 +1,35 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ +#ifndef RTR_HT_SPKITABLE_PRIVATE_H +#define RTR_HT_SPKITABLE_PRIVATE_H + +#include "rtrlib/spki/spkitable_private.h" + +#include "third-party/tommyds/tommyhashlin.h" +#include "third-party/tommyds/tommylist.h" + +typedef int (*hash_cmp_fp)(const void *arg, const void *obj); + +/** + * @brief spki_table. + * @param hashtable Linear hashtable + * @param list List that holds the same entries as hashtable, used to iterate. + * @param cmp_fp Compare function used to find entries in the hashtable + * @param update_fp Update function, called when the hashtable changes + * @param lock Read-Write lock to prevent data races + */ +struct spki_table { + tommy_hashlin hashtable; + tommy_list list; + hash_cmp_fp cmp_fp; + spki_update_fp update_fp; + pthread_rwlock_t lock; +}; + +#endif diff --git a/rtrlib/spki/spkitable.h b/rtrlib/spki/spkitable.h new file mode 100644 index 0000000..4158c58 --- /dev/null +++ b/rtrlib/spki/spkitable.h @@ -0,0 +1,54 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_spki_h Subject Public Key Info table + * @brief The spki_table is an abstract data structure to organize the received Router Key PDUs + * from a RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_SPKI_H +#define RTR_SPKI_H + +#include "rtrlib/rtr/rtr.h" + +#include <stdbool.h> +#include <stdint.h> + +#define SKI_SIZE 20 +#define SPKI_SIZE 91 + +struct spki_table; + +/** + * @brief spki_record. + * @param ski Subject Key Identifier + * @param asn Origin AS number + * @param spki Subject public key info + * @param socket Pointer to the rtr_socket this spki_record was received in + */ +struct spki_record { + uint8_t ski[SKI_SIZE]; + uint32_t asn; + uint8_t spki[SPKI_SIZE]; + const struct rtr_socket *socket; +}; + +/** + * @brief A function pointer that is called if an record was added + * to the spki_table or was removed from the spki_table. + * @param spki_table which was updated. + * @param record spki_record that was modified. + * @param added True if the record was added, false if the record was removed. + */ +typedef void (*spki_update_fp)(struct spki_table *spki_table, const struct spki_record record, const bool added); +#endif +/** @} */ diff --git a/rtrlib/spki/spkitable_private.h b/rtrlib/spki/spkitable_private.h new file mode 100644 index 0000000..65c3481 --- /dev/null +++ b/rtrlib/spki/spkitable_private.h @@ -0,0 +1,143 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_spki_h Subject Public Key Info table + * @brief The spki_table is an abstract data structure to organize the received Router Key PDUs + * from a RPKI-RTR cache server. + * + * @{ + */ + +#ifndef RTR_SPKI_PRIVATE_H +#define RTR_SPKI_PRIVATE_H + +#include "rtrlib/spki/spkitable.h" + +#include <stdint.h> + +/** + * @brief Possible return values for some spki_table_ functions. + */ +enum spki_rtvals { + /** Operation was successful. */ + SPKI_SUCCESS = 0, + + /** Error occurred. */ + SPKI_ERROR = -1, + + /** The supplied spki_record already exists in the spki_table. */ + SPKI_DUPLICATE_RECORD = -2, + + /** spki_record wasn't found in the spki_table. */ + SPKI_RECORD_NOT_FOUND = -3 +}; + +/** + * @brief Initializes the spki_table struct. + * @param[in] spki_table spki_table that will be initialized. + * @param[in] update_fp Pointer to update function + */ +void spki_table_init(struct spki_table *spki_table, spki_update_fp update_fp); + +/** + * @brief Frees the memory associated with the spki_table. + * @param[in] spki_table spki_table that will be initialized. + */ +void spki_table_free(struct spki_table *spki_table); + +/** + * @brief Frees the memory associated with the spki_table without calling the update callback. + * @param[in] spki_table spki_table that will be initialized. + */ +void spki_table_free_without_notify(struct spki_table *spki_table); + +/** + * @brief Adds a spki_record to a spki_table. + * @param[in] spki_table spki_table to use. + * @param[in] spki_record spki_record that will be added. + * @return SPKI_SUCCESS On success. + * @return SPKI_ERROR On error. + * @return SPKI_DUPLICATE_RECORD If an identical spki_record already exists + */ +int spki_table_add_entry(struct spki_table *spki_table, struct spki_record *spki_record); + +/** + * @brief Returns all spki_record whose ASN and SKI matches. + * @param[in] spki_table spki_table to use + * @param[in] asn the AS number + * @param[in] ski the 20 byte field which contains the SKI to search for + * @param[out] result the result array. NULL if no records could be found + * @param[out] result_size elment count of the result array + * @return SPKI_SUCCESS On success + * @return SPKI_ERROR On error + */ +int spki_table_get_all(struct spki_table *spki_table, uint32_t asn, uint8_t *ski, struct spki_record **result, + unsigned int *result_size); + +/** + * @brief Returns all spki_record whose SKI number matches the given one. + * @param[in] spki_table spki_table to use + * @param[in] ski the 20 byte field which contains the SKI to search for + * @param[out] result the result array. NULL if no records could be found + * @param[out] result_size elment count of the result array + * @return SPKI_SUCCESS On success + * @return SPKI_ERROR On error + */ +int spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result, + unsigned int *result_size); + +/** + * @brief Removes spki_record from spki_table + * @param spki_table spki_table to use + * @param spki_record spki_record to remove; + * @return SPKI_SUCCESS On success + * @return SPKI_ERROR On error + * @return SPKI_RECORD_NOT_FOUND On record not found + */ +int spki_table_remove_entry(struct spki_table *spki_table, struct spki_record *spki_record); + +/** + * @brief Removes all entries in the spki_table that match the passed socket_id. + * @param[in] spki_table spki_table to use. + * @param[in] socket origin socket of the record + * @return SPKI_SUCCESS On success. + * @return SPKI_ERROR On error. + */ +int spki_table_src_remove(struct spki_table *spki_table, const struct rtr_socket *socket); + +/** + * @brief Copy spki table except entries from the given socket + * @param[in] src source table + * @param[in] dest target table + * @param[in] socket socket which entries should not be copied + * @return SPKI_SUCCESS On success. + * @return SPKI_ERROR On error. + */ +int spki_table_copy_except_socket(struct spki_table *src, struct spki_table *dest, struct rtr_socket *socket); + +/** + * @brief Notify client about changes between two spki tables regarding one specific socket + * @details old_table will be modified and should probebly be freed after calling this function + * @param[in] new_table + * @param[in] old_table + * @param[in] socket socket which entries should be diffed + */ +void spki_table_notify_diff(struct spki_table *new_table, struct spki_table *old_table, + const struct rtr_socket *socket); + +/** + * @brief tommy_hashlin and tommy_list of the argument tables + * @param[in] a + * @param[in] b + */ +void spki_table_swap(struct spki_table *a, struct spki_table *b); + +#endif +/** @} */ diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c new file mode 100644 index 0000000..222ea25 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.c @@ -0,0 +1,392 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "ssh_transport_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <errno.h> +#include <libssh/libssh.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> + +#define SSH_DBG(fmt, sock, ...) \ + do { \ + const struct tr_ssh_socket *tmp = sock; \ + lrtr_dbg("SSH Transport(%s@%s:%u): " fmt, tmp->config.username, tmp->config.host, tmp->config.port, \ + ##__VA_ARGS__); \ + } while (0) +#define SSH_DBG1(a, sock) SSH_DBG(a, sock) + +struct tr_ssh_socket { + ssh_session session; + ssh_channel channel; + struct tr_ssh_config config; + char *ident; +}; + +static int tr_ssh_open(void *tr_ssh_sock); +static void tr_ssh_close(void *tr_ssh_sock); +static void tr_ssh_free(struct tr_socket *tr_sock); +static int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout); +static int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, const time_t timeout); +static int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len); +static const char *tr_ssh_ident(void *tr_ssh_sock); + +/* WARNING: This function has cancelable sections! */ +int tr_ssh_open(void *socket) +{ + struct tr_ssh_socket *ssh_socket = socket; + const struct tr_ssh_config *config = &ssh_socket->config; + + assert(!ssh_socket->channel); + assert(!ssh_socket->session); + + ssh_socket->session = ssh_new(); + if (!ssh_socket->session) { + SSH_DBG("%s: can't create ssh_session", ssh_socket, __func__); + goto error; + } + + const int verbosity = SSH_LOG_NOLOG; + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_options_set(ssh_socket->session, SSH_OPTIONS_HOST, config->host); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_PORT, &(config->port)); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_BINDADDR, config->bindaddr); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_USER, config->username); + + if (config->server_hostkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_KNOWNHOSTS, config->server_hostkey_path); + + if (config->client_privkey_path) + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + if (config->new_socket) { + int fd; + + fd = config->new_socket(config->data); + if (fd >= 0) { + ssh_options_set(ssh_socket->session, SSH_OPTIONS_FD, &fd); + } else { + SSH_DBG1("tr_ssh_init: opening SSH connection failed", ssh_socket); + goto error; + } + } + + ssh_set_blocking(ssh_socket->session, 0); + int ret; + + do { + ret = ssh_connect(ssh_socket->session); + + if (ret == SSH_ERROR) { + SSH_DBG("%s: opening SSH connection failed", ssh_socket, __func__); + goto error; + } else if (ret == SSH_AGAIN) { + socket_t fd = ssh_get_fd(ssh_socket->session); + + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + struct timeval timeout = {.tv_sec = ssh_socket->config.connect_timeout, .tv_usec = 0}; + int oldcancelstate; + + /* Enable cancellability for the select call + * to prevent rtr_stop from blocking for a long time. + * It must be the only blocking call in this function. + * Since local resources have all been freed this should be safe. + */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int sret = select(fd + 1, &rfds, NULL, NULL, &timeout); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (sret < 0) { + SSH_DBG("could not select ssh socket, %s", ssh_socket, strerror(errno)); + + } else if (sret == 0) { + SSH_DBG1("connection attempt timed out", ssh_socket); + goto error; + } + } + + } while (ret != SSH_OK); + + ssh_set_blocking(ssh_socket->session, 1); + + // check server identity +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 8 + if ((config->server_hostkey_path) && (ssh_session_is_known_server(ssh_socket->session) != SSH_KNOWN_HOSTS_OK)) { +#else + if ((config->server_hostkey_path) && (ssh_is_server_known(ssh_socket->session) != SSH_SERVER_KNOWN_OK)) { +#endif + SSH_DBG("%s: Wrong hostkey", ssh_socket, __func__); + goto error; + } + + if (config->client_privkey_path) { + SSH_DBG("%s: Trying publickey authentication", ssh_socket, __func__); + ssh_options_set(ssh_socket->session, SSH_OPTIONS_IDENTITY, config->client_privkey_path); + + int rtval; + +#if LIBSSH_VERSION_MAJOR > 0 || LIBSSH_VERSION_MINOR > 5 + rtval = ssh_userauth_publickey_auto(ssh_socket->session, NULL, NULL); +#else /* else use libSSH version 0.5.0 */ + rtval = ssh_userauth_autopubkey(ssh_socket->session, NULL); +#endif + if (rtval != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Publickey authentication failed", ssh_socket, __func__); + goto error; + } + + } else { + SSH_DBG("%s: Trying password authentication", ssh_socket, __func__); + + if (ssh_userauth_password(ssh_socket->session, NULL, config->password) != SSH_AUTH_SUCCESS) { + SSH_DBG("%s: Password authentication failed", ssh_socket, __func__); + goto error; + } + } + + ssh_socket->channel = ssh_channel_new(ssh_socket->session); + if (!ssh_socket->channel) + goto error; + + if (ssh_channel_open_session(ssh_socket->channel) == SSH_ERROR) + goto error; + + if (ssh_channel_request_subsystem(ssh_socket->channel, "rpki-rtr") == SSH_ERROR) { + SSH_DBG("%s: Error requesting subsystem rpki-rtr", ssh_socket, __func__); + goto error; + } + SSH_DBG1("Connection established", ssh_socket); + + return TR_SUCCESS; + +error: + tr_ssh_close(ssh_socket); + return TR_ERROR; +} + +void tr_ssh_close(void *tr_ssh_sock) +{ + struct tr_ssh_socket *socket = tr_ssh_sock; + + if (socket->channel) { + if (ssh_channel_is_open(socket->channel)) + ssh_channel_close(socket->channel); + ssh_channel_free(socket->channel); + socket->channel = NULL; + } + if (socket->session) { + ssh_disconnect(socket->session); + ssh_free(socket->session); + socket->session = NULL; + } + SSH_DBG1("Socket closed", socket); +} + +void tr_ssh_free(struct tr_socket *tr_sock) +{ + struct tr_ssh_socket *tr_ssh_sock = tr_sock->socket; + + assert(!tr_ssh_sock->channel); + assert(!tr_ssh_sock->session); + + SSH_DBG1("Freeing socket", tr_ssh_sock); + + lrtr_free(tr_ssh_sock->config.host); + lrtr_free(tr_ssh_sock->config.bindaddr); + lrtr_free(tr_ssh_sock->config.username); + lrtr_free(tr_ssh_sock->config.client_privkey_path); + lrtr_free(tr_ssh_sock->config.server_hostkey_path); + + if (tr_ssh_sock->ident) + lrtr_free(tr_ssh_sock->ident); + lrtr_free(tr_ssh_sock); + tr_sock->socket = NULL; +} + +int tr_ssh_recv_async(const struct tr_ssh_socket *tr_ssh_sock, void *buf, const size_t buf_len) +{ + const int rtval = ssh_channel_read_nonblocking(tr_ssh_sock->channel, buf, buf_len, false); + + if (rtval == 0) { + if (ssh_channel_is_eof(tr_ssh_sock->channel) != 0) { + SSH_DBG1("remote has sent EOF", tr_ssh_sock); + return TR_CLOSED; + } else { + return TR_WOULDBLOCK; + } + } else if (rtval == SSH_ERROR) { + SSH_DBG1("recv(..) error", tr_ssh_sock); + return TR_ERROR; + } + return rtval; +} + +int tr_ssh_recv(const void *tr_ssh_sock, void *buf, const size_t buf_len, const time_t timeout) +{ + ssh_channel rchans[2] = {((struct tr_ssh_socket *)tr_ssh_sock)->channel, NULL}; + struct timeval timev = {timeout, 0}; + int ret; + + ret = ssh_channel_select(rchans, NULL, NULL, &timev); + if (ret == SSH_EINTR) + return TR_INTR; + else if (ret == SSH_ERROR) + return TR_ERROR; + + if (ssh_channel_is_eof(((struct tr_ssh_socket *)tr_ssh_sock)->channel) != 0) + return TR_ERROR; + + if (!rchans[0]) + return TR_WOULDBLOCK; + + return tr_ssh_recv_async(tr_ssh_sock, buf, buf_len); +} + +int tr_ssh_send(const void *tr_ssh_sock, const void *pdu, const size_t len, + const time_t timeout __attribute__((unused))) +{ + int ret = ssh_channel_write(((struct tr_ssh_socket *)tr_ssh_sock)->channel, pdu, len); + + if (ret == SSH_ERROR) + return TR_ERROR; + + return ret; +} + +const char *tr_ssh_ident(void *tr_ssh_sock) +{ + size_t len; + struct tr_ssh_socket *sock = tr_ssh_sock; + + assert(sock); + + if (sock->ident) + return sock->ident; + + len = strlen(sock->config.username) + 1 + strlen(sock->config.host) + 1 + 5 + 1; + sock->ident = lrtr_malloc(len); + if (!sock->ident) + return NULL; + snprintf(sock->ident, len, "%s@%s:%u", sock->config.username, sock->config.host, sock->config.port); + return sock->ident; +} + +RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket) +{ + socket->close_fp = &tr_ssh_close; + socket->free_fp = &tr_ssh_free; + socket->open_fp = &tr_ssh_open; + socket->recv_fp = &tr_ssh_recv; + socket->send_fp = &tr_ssh_send; + socket->ident_fp = &tr_ssh_ident; + + socket->socket = lrtr_calloc(1, sizeof(struct tr_ssh_socket)); + struct tr_ssh_socket *ssh_socket = socket->socket; + + ssh_socket->channel = NULL; + ssh_socket->session = NULL; + ssh_socket->config.host = lrtr_strdup(config->host); + if (!ssh_socket->config.host) + goto error; + ssh_socket->config.port = config->port; + + ssh_socket->config.username = lrtr_strdup(config->username); + if (!ssh_socket->config.username) + goto error; + + if ((config->password && config->client_privkey_path) || (!config->password && !config->client_privkey_path)) + return TR_ERROR; + + if (config->bindaddr) { + ssh_socket->config.bindaddr = lrtr_strdup(config->bindaddr); + + if (!ssh_socket->config.bindaddr) + goto error; + + } else { + ssh_socket->config.bindaddr = NULL; + } + + if (config->client_privkey_path) { + ssh_socket->config.client_privkey_path = lrtr_strdup(config->client_privkey_path); + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.client_privkey_path = NULL; + } + + if (config->server_hostkey_path) { + ssh_socket->config.server_hostkey_path = lrtr_strdup(config->server_hostkey_path); + + if (!ssh_socket->config.client_privkey_path) + goto error; + + } else { + ssh_socket->config.server_hostkey_path = NULL; + } + + if (config->connect_timeout == 0) + ssh_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT; + else + ssh_socket->config.connect_timeout = config->connect_timeout; + + if (config->password) { + ssh_socket->config.password = lrtr_strdup(config->password); + + if (!ssh_socket->config.password) + goto error; + + } else { + ssh_socket->config.password = NULL; + } + + ssh_socket->ident = NULL; + ssh_socket->config.data = config->data; + ssh_socket->config.new_socket = config->new_socket; + + return TR_SUCCESS; + +error: + if (ssh_socket->config.host) + free(ssh_socket->config.host); + + if (ssh_socket->config.username) + free(ssh_socket->config.username); + + if (ssh_socket->config.bindaddr) + free(ssh_socket->config.bindaddr); + + if (ssh_socket->config.client_privkey_path) + free(ssh_socket->config.client_privkey_path); + + if (ssh_socket->config.server_hostkey_path) + free(ssh_socket->config.server_hostkey_path); + + if (ssh_socket->config.password) + free(ssh_socket->config.password); + + return TR_ERROR; +} diff --git a/rtrlib/transport/ssh/ssh_transport.h b/rtrlib/transport/ssh/ssh_transport.h new file mode 100644 index 0000000..188e256 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport.h @@ -0,0 +1,74 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_ssh_transport_h SSH transport socket + * @ingroup mod_transport_h + * @brief An implementation of the SSH protocol for the RTR transport. + * @details This transport implementation uses libssh + * (http://www.libssh.org/) for all ssh specific operations.\n + * See @ref mod_transport_h "transport interface" for a list of supported + * operations. + * + * @{ + * + * @example ssh_tr.c + * Example of how to open a SSH transport connection. + */ + +#ifndef SSH_TRANSPORT_H +#define SSH_TRANSPORT_H + +#include "rtrlib/transport/transport.h" + +/** + * @brief A tr_ssh_config struct holds configuration data for an tr_ssh socket. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * @param username Username for authentication. + * @param server_hostkey_path Path to public SSH key of the server or NULL to + * don't verify host authenticity. + * @param client_privkey_path Path to private key of the authentication keypair + * or NULL to use ~/.ssh/id_rsa. + * @param data Information to pass to callback function + * in charge of retrieving socket + * @param new_socket(void *opaque_info) callback routine, that + * Pointer to the function that is called every time a new connection + * is made. The returned socket is expected to be ready for use (e.g. + * in state established), and must use a reliably stream-oriented transport. + * When new_socket() is used, host, port, and bindaddr are not used. + * @param connect_timeout Time in seconds to wait for a successful connection. + * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT + */ +struct tr_ssh_config { + char *host; + unsigned int port; + char *bindaddr; + char *username; + char *server_hostkey_path; + char *client_privkey_path; + void *data; + int (*new_socket)(void *data); + unsigned int connect_timeout; + char *password; +}; + +/** + * @brief Initializes the tr_socket struct for a SSH connection. + * @param[in] config SSH configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_ssh_init(const struct tr_ssh_config *config, struct tr_socket *socket); + +#endif +/** @} */ diff --git a/rtrlib/transport/ssh/ssh_transport_private.h b/rtrlib/transport/ssh/ssh_transport_private.h new file mode 100644 index 0000000..b574863 --- /dev/null +++ b/rtrlib/transport/ssh/ssh_transport_private.h @@ -0,0 +1,13 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#ifndef SSH_TRANSPORT_PRIVATE_H +#define SSH_TRANSPORT_PRIVATE_H +#include "ssh_transport.h" +#endif diff --git a/rtrlib/transport/tcp/tcp_transport.c b/rtrlib/transport/tcp/tcp_transport.c new file mode 100644 index 0000000..1dbc903 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport.c @@ -0,0 +1,368 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "tcp_transport_private.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/transport/transport_private.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#define TCP_DBG(fmt, sock, ...) \ + do { \ + const struct tr_tcp_socket *tmp = sock; \ + lrtr_dbg("TCP Transport(%s:%s): " fmt, tmp->config.host, tmp->config.port, ##__VA_ARGS__); \ + } while (0) +#define TCP_DBG1(a, sock) TCP_DBG(a, sock) + +struct tr_tcp_socket { + int socket; + struct tr_tcp_config config; + char *ident; +}; + +static int tr_tcp_open(void *tr_tcp_sock); +static void tr_tcp_close(void *tr_tcp_sock); +static void tr_tcp_free(struct tr_socket *tr_sock); +static int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout); +static int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout); +static const char *tr_tcp_ident(void *socket); + +static int set_socket_blocking(int socket) +{ + int flags = fcntl(socket, F_GETFL); + + if (flags == -1) + return TR_ERROR; + + flags &= ~O_NONBLOCK; + + if (fcntl(socket, F_SETFL, flags) == -1) + return TR_ERROR; + + return TR_SUCCESS; +} + +static int set_socket_non_blocking(int socket) +{ + int flags = fcntl(socket, F_GETFL); + + if (flags == -1) + return TR_ERROR; + + flags |= O_NONBLOCK; + + if (fcntl(socket, F_SETFL, flags) == -1) + return TR_ERROR; + + return TR_SUCCESS; +} + +static int get_socket_error(int socket) +{ + int result; + socklen_t result_len = sizeof(result); + + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &result, &result_len) < 0) + return TR_ERROR; + + return result; +} + +/* WARNING: This function has cancelable sections! */ +int tr_tcp_open(void *tr_socket) +{ + int rtval = TR_ERROR; + int tcp_rtval = 0; + struct tr_tcp_socket *tcp_socket = tr_socket; + const struct tr_tcp_config *config = &tcp_socket->config; + + assert(tcp_socket->socket == -1); + + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *bind_addrinfo = NULL; + + bzero(&hints, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + if (config->new_socket) { + tcp_socket->socket = (*config->new_socket)(config->data); + if (tcp_socket->socket <= 0) { + TCP_DBG("Couldn't establish TCP connection, %s", + tcp_socket, strerror(errno)); + goto end; + } + } + if (tcp_socket->socket < 0) { + tcp_rtval = getaddrinfo(config->host, config->port, &hints, &res); + if (tcp_rtval != 0) { + if (tcp_rtval == EAI_SYSTEM) { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + strerror(errno)); + } else { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + gai_strerror(tcp_rtval)); + } + return TR_ERROR; + } + + tcp_socket->socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (tcp_socket->socket == -1) { + TCP_DBG("Socket creation failed, %s", tcp_socket, strerror(errno)); + goto end; + } + + if (tcp_socket->config.bindaddr) { + tcp_rtval = getaddrinfo(tcp_socket->config.bindaddr, 0, &hints, &bind_addrinfo); + if (tcp_rtval != 0) { + if (tcp_rtval == EAI_SYSTEM) { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + strerror(errno)); + } else { + TCP_DBG("getaddrinfo error, %s", tcp_socket, + gai_strerror(tcp_rtval)); + } + goto end; + } + if (bind(tcp_socket->socket, bind_addrinfo->ai_addr, bind_addrinfo->ai_addrlen) != 0) { + TCP_DBG("Socket bind failed, %s", tcp_socket, strerror(errno)); + goto end; + } + + freeaddrinfo(bind_addrinfo); + bind_addrinfo = NULL; + } + + if (set_socket_non_blocking(tcp_socket->socket) == TR_ERROR) { + TCP_DBG("Could not set socket to non blocking, %s", tcp_socket, strerror(errno)); + goto end; + } + + if (connect(tcp_socket->socket, res->ai_addr, res->ai_addrlen) == -1 && errno != EINPROGRESS) { + TCP_DBG("Couldn't establish TCP connection, %s", + tcp_socket, strerror(errno)); + goto end; + } + + freeaddrinfo(res); + res = NULL; + + fd_set wfds; + + FD_ZERO(&wfds); + FD_SET(tcp_socket->socket, &wfds); + + struct timeval timeout = {.tv_sec = tcp_socket->config.connect_timeout, .tv_usec = 0}; + int oldcancelstate; + + /* Enable cancellability for the select call + * to prevent rtr_stop from blocking for a long time. + * It must be the only blocking call in this function. + * Since local resources have all been freed this should be safe. + */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate); + int ret = select(tcp_socket->socket + 1, NULL, &wfds, NULL, &timeout); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldcancelstate); + + if (ret < 0) { + TCP_DBG("Could not select tcp socket, %s", tcp_socket, strerror(errno)); + goto end; + + } else if (ret == 0) { + TCP_DBG("Could not establish TCP connection in time", tcp_socket); + goto end; + } + + int socket_error = get_socket_error(tcp_socket->socket); + + if (socket_error == TR_ERROR) { + TCP_DBG("Could not get socket error, %s", tcp_socket, strerror(errno)); + goto end; + + } else if (socket_error > 0) { + TCP_DBG("Could not establish TCP connection, %s", tcp_socket, strerror(socket_error)); + goto end; + } + + if (set_socket_blocking(tcp_socket->socket) == TR_ERROR) { + TCP_DBG("Could not set socket to blocking, %s", tcp_socket, strerror(errno)); + goto end; + } + } + + TCP_DBG1("Connection established", tcp_socket); + rtval = TR_SUCCESS; + +end: + if (res) + freeaddrinfo(res); + + if (bind_addrinfo) + freeaddrinfo(bind_addrinfo); + if (rtval == -1) + tr_tcp_close(tr_socket); + return rtval; +} + +void tr_tcp_close(void *tr_tcp_sock) +{ + struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + + if (tcp_socket->socket != -1) + close(tcp_socket->socket); + TCP_DBG1("Socket closed", tcp_socket); + tcp_socket->socket = -1; +} + +void tr_tcp_free(struct tr_socket *tr_sock) +{ + struct tr_tcp_socket *tcp_sock = tr_sock->socket; + + assert(tcp_sock); + assert(tcp_sock->socket == -1); + + TCP_DBG1("Freeing socket", tcp_sock); + + lrtr_free(tcp_sock->config.host); + lrtr_free(tcp_sock->config.port); + lrtr_free(tcp_sock->config.bindaddr); + + if (tcp_sock->ident) + lrtr_free(tcp_sock->ident); + tr_sock->socket = NULL; + lrtr_free(tcp_sock); +} + +int tr_tcp_recv(const void *tr_tcp_sock, void *pdu, const size_t len, const time_t timeout) +{ + const struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + int rtval; + + if (timeout == 0) { + rtval = recv(tcp_socket->socket, pdu, len, MSG_DONTWAIT); + } else { + struct timeval t = {timeout, 0}; + + if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) == -1) { + TCP_DBG("setting SO_RCVTIMEO failed, %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + rtval = recv(tcp_socket->socket, pdu, len, 0); + } + + if (rtval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return TR_WOULDBLOCK; + if (errno == EINTR) + return TR_INTR; + TCP_DBG("recv(..) error: %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + if (rtval == 0) + return TR_CLOSED; + return rtval; +} + +int tr_tcp_send(const void *tr_tcp_sock, const void *pdu, const size_t len, const time_t timeout) +{ + const struct tr_tcp_socket *tcp_socket = tr_tcp_sock; + int rtval; + + if (timeout == 0) { + rtval = send(tcp_socket->socket, pdu, len, MSG_DONTWAIT); + } else { + struct timeval t = {timeout, 0}; + + if (setsockopt(tcp_socket->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) == -1) { + TCP_DBG("setting SO_SNDTIMEO failed, %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + rtval = send(tcp_socket->socket, pdu, len, 0); + } + + if (rtval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return TR_WOULDBLOCK; + if (errno == EINTR) + return TR_INTR; + TCP_DBG("send(..) error: %s", tcp_socket, strerror(errno)); + return TR_ERROR; + } + if (rtval == 0) + return TR_ERROR; + return rtval; +} + +const char *tr_tcp_ident(void *socket) +{ + size_t len; + struct tr_tcp_socket *sock = socket; + + assert(sock); + + if (sock->ident) + return sock->ident; + + len = strlen(sock->config.port) + strlen(sock->config.host) + 2; + sock->ident = lrtr_malloc(len); + if (!sock->ident) + return NULL; + snprintf(sock->ident, len, "%s:%s", sock->config.host, sock->config.port); + + return sock->ident; +} + +RTRLIB_EXPORT int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket) +{ + socket->close_fp = &tr_tcp_close; + socket->free_fp = &tr_tcp_free; + socket->open_fp = &tr_tcp_open; + socket->recv_fp = &tr_tcp_recv; + socket->send_fp = &tr_tcp_send; + socket->ident_fp = &tr_tcp_ident; + + socket->socket = lrtr_malloc(sizeof(struct tr_tcp_socket)); + struct tr_tcp_socket *tcp_socket = socket->socket; + + tcp_socket->socket = -1; + tcp_socket->config.host = lrtr_strdup(config->host); + tcp_socket->config.port = lrtr_strdup(config->port); + if (config->bindaddr) + tcp_socket->config.bindaddr = lrtr_strdup(config->bindaddr); + else + tcp_socket->config.bindaddr = NULL; + + if (config->connect_timeout == 0) + tcp_socket->config.connect_timeout = RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT; + else + tcp_socket->config.connect_timeout = config->connect_timeout; + + tcp_socket->ident = NULL; + tcp_socket->config.data = config->data; + tcp_socket->config.new_socket = config->new_socket; + + return TR_SUCCESS; +} diff --git a/rtrlib/transport/tcp/tcp_transport.h b/rtrlib/transport/tcp/tcp_transport.h new file mode 100644 index 0000000..3198050 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport.h @@ -0,0 +1,59 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_tcp_transport_h TCP transport socket + * @ingroup mod_transport_h + * @brief An implementation of the TCP protocol for the RTR transport. + * See @ref mod_transport_h "transport interface" for a list of supported operations. + * + * @{ + */ + +#ifndef RTR_TCP_TRANSPORT_H +#define RTR_TCP_TRANSPORT_H + +#include "rtrlib/transport/transport.h" + +/** + * @brief A tr_tcp_config struct holds configuration for a TCP connection. + * @param host Hostname or IP address to connect to. + * @param port Port to connect to. + * @param bindaddr Hostname or IP address to connect from. NULL for + * determination by OS. + * to use the source address of the system's default route to the server + * @param data Information to pass to callback function + * in charge of retrieving socket + * @param new_socket(void *opaque_info) callback routine, that + * Pointer to the function that is called every time a new connection + * is made. The returned socket is expected to be ready for use (e.g. + * in state established), and must use a reliably stream-oriented transport. + * When new_socket() is used, host, port, and bindaddr are not used. + * @param connect_timeout Time in seconds to wait for a successful connection. + * Defaults to #RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT + */ +struct tr_tcp_config { + char *host; + char *port; + char *bindaddr; + void *data; + int (*new_socket)(void *data); + unsigned int connect_timeout; +}; + +/** + * @brief Initializes the tr_socket struct for a TCP connection. + * @param[in] config TCP configuration for the connection. + * @param[out] socket Initialized transport socket. + * @returns TR_SUCCESS On success. + * @returns TR_ERROR On error. + */ +int tr_tcp_init(const struct tr_tcp_config *config, struct tr_socket *socket); +#endif +/** @} */ diff --git a/rtrlib/transport/tcp/tcp_transport_private.h b/rtrlib/transport/tcp/tcp_transport_private.h new file mode 100644 index 0000000..ef41a32 --- /dev/null +++ b/rtrlib/transport/tcp/tcp_transport_private.h @@ -0,0 +1,23 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_tcp_transport_h TCP transport socket + * @ingroup mod_transport_h + * @brief An implementation of the TCP protocol for the RTR transport. + * See @ref mod_transport_h "transport interface" for a list of supported operations. + * + * @{ + */ + +#ifndef RTR_TCP_TRANSPORT_PRIVATE_H +#define RTR_TCP_TRANSPORT_PRIVATE_H +#include "tcp_transport.h" +#endif +/** @} */ diff --git a/rtrlib/transport/transport.c b/rtrlib/transport/transport.c new file mode 100644 index 0000000..d1bdfc5 --- /dev/null +++ b/rtrlib/transport/transport.c @@ -0,0 +1,87 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "transport_private.h" + +#include "rtrlib/lib/utils_private.h" + +inline int tr_open(struct tr_socket *socket) +{ + return socket->open_fp(socket->socket); +} + +inline void tr_close(struct tr_socket *socket) +{ + socket->close_fp(socket->socket); +} + +inline void tr_free(struct tr_socket *socket) +{ + socket->free_fp(socket); +} + +inline int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + return socket->send_fp(socket->socket, pdu, len, timeout); +} + +inline int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout) +{ + return socket->recv_fp(socket->socket, buf, len, timeout); +} + +/* cppcheck-suppress unusedFunction */ +inline const char *tr_ident(struct tr_socket *sock) +{ + return sock->ident_fp(sock->socket); +} + +int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + unsigned int total_send = 0; + time_t end_time; + + lrtr_get_monotonic_time(&end_time); + end_time = end_time + timeout; + + while (total_send < len) { + time_t cur_time; + int rtval; + + lrtr_get_monotonic_time(&cur_time); + + rtval = tr_send(socket, ((char *)pdu) + total_send, (len - total_send), (end_time - cur_time)); + if (rtval < 0) + return rtval; + total_send += rtval; + } + return total_send; +} + +int tr_recv_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + size_t total_recv = 0; + time_t end_time; + + lrtr_get_monotonic_time(&end_time); + end_time += timeout; + + while (total_recv < len) { + time_t cur_time; + int rtval; + + lrtr_get_monotonic_time(&cur_time); + + rtval = tr_recv(socket, ((char *)pdu) + total_recv, (len - total_recv), end_time - cur_time); + if (rtval < 0) + return rtval; + total_recv += rtval; + } + return total_recv; +} diff --git a/rtrlib/transport/transport.h b/rtrlib/transport/transport.h new file mode 100644 index 0000000..c092a2f --- /dev/null +++ b/rtrlib/transport/transport.h @@ -0,0 +1,113 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_transport_h Transport sockets + * @brief The RTR transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RTR server and client. + * @details Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * tr_tcp_init()).\n + * The tr_* functions call the corresponding function pointers, which are + * passed in the tr_socket struct, and forward the remaining arguments. + * + * @{ + */ + +#ifndef RTR_TRANSPORT_H +#define RTR_TRANSPORT_H + +#include <time.h> + +/** + * @brief Default connect timeout + */ +#define RTRLIB_TRANSPORT_CONNECT_TIMEOUT_DEFAULT 30 + +/** + * @brief The return values for tr_ functions. + */ +enum tr_rtvals { + /** @brief Operation was successful. */ + TR_SUCCESS = 0, + + /** Error occurred. */ + TR_ERROR = -1, + + /** No data is available on the socket. */ + TR_WOULDBLOCK = -2, + + /** Call was interrupted from a signal */ + TR_INTR = -3, + + /** Connection closed */ + TR_CLOSED = -4 +}; + +struct tr_socket; + +/** + * @brief A function pointer to a technology specific close function. + * \sa tr_close + */ +typedef void (*tr_close_fp)(void *socket); + +/** + * @brief A function pointer to a technology specific open function. + * \sa tr_open + */ +typedef int (*tr_open_fp)(void *socket); + +/** + * @brief A function pointer to a technology specific free function. + * All memory associated with the tr_socket will be freed. + * \sa tr_free + */ +typedef void (*tr_free_fp)(struct tr_socket *tr_sock); + +/** + * @brief A function pointer to a technology specific recv function. + * \sa tr_recv + */ +typedef int (*tr_recv_fp)(const void *socket, void *pdu, const size_t len, const time_t timeout); + +/** + * @brief A function pointer to a technology specific send function. + * \sa tr_send + */ +typedef int (*tr_send_fp)(const void *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * @brief A function pointer to a technology specific info function. + * \sa tr_send + */ +typedef const char *(*tr_ident_fp)(void *socket); + +/** + * @brief A transport socket datastructure. + * + * @param socket A pointer to a technology specific socket. + * @param open_fp Pointer to a function that establishes the socket connection. + * @param close_fp Pointer to a function that closes the socket. + * @param free_fp Pointer to a function that frees all memory allocated with this socket. + * @param send_fp Pointer to a function that sends data through this socket. + * @param recv_fp Pointer to a function that receives data from this socket. + */ +struct tr_socket { + void *socket; + tr_open_fp open_fp; + tr_close_fp close_fp; + tr_free_fp free_fp; + tr_send_fp send_fp; + tr_recv_fp recv_fp; + tr_ident_fp ident_fp; +}; + +#endif +/** @} */ diff --git a/rtrlib/transport/transport_private.h b/rtrlib/transport/transport_private.h new file mode 100644 index 0000000..7fc2e19 --- /dev/null +++ b/rtrlib/transport/transport_private.h @@ -0,0 +1,107 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/** + * @defgroup mod_transport_h Transport sockets + * @brief The RTR transport sockets implement the communication channel + * (e.g., SSH, TCP, TCP-AO) between an RTR server and client. + * @details Before using the transport socket, a tr_socket must be + * initialized based on a protocol-dependent init function (e.g., + * tr_tcp_init()).\n + * The tr_* functions call the corresponding function pointers, which are + * passed in the tr_socket struct, and forward the remaining arguments. + * + * @{ + */ + +#ifndef RTR_TRANSPORT_PRIVATE_H +#define RTR_TRANSPORT_PRIVATE_H + +#include "transport.h" + +#include <time.h> + +/** + * @brief Establish the connection. + * @param[in] socket Socket that will be used. + * @return TR_SUCCESS On success. + * @return TR_ERROR On error. + */ +int tr_open(struct tr_socket *socket); + +/** + * @brief Close the socket connection. + * @param[in] socket Socket that will be closed. + */ +void tr_close(struct tr_socket *socket); + +/** + * @brief Deallocates all memory that the passed socket uses. + * Socket have to be closed before. + * @param[in] socket which will be freed. + */ +void tr_free(struct tr_socket *socket); + +/** + * @brief Receives <= len Bytes data from the socket. + * @param[in] socket Socket that will be used. + * @param[out] buf Received data, must be an allocated memory area of >=pdu_len bytes. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the function will block till len data was received. + * @return >0 Number of Bytes read. + * @return TR_ERROR On error. + * @return TR_WOULDBLOCK If no data was available at the socket before the timeout expired. + */ +int tr_recv(const struct tr_socket *socket, void *buf, const size_t len, const time_t timeout); + +/** + * @brief Send <= len Bytes data over the socket. + * @param[in] socket Socket that will be used. + * @param[out] pdu Data that will be be sent. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the function should try to send the data till it returns. + * @return >0 Number of Bytes sent. + * @return TR_ERROR On error. + */ +int tr_send(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * Repeatedly calls tr_send(..) till len Bytes were sent, the timeout expired or an error occurred. + * @param[in] socket Socket that will be used. + * @param[out] pdu Data that will be be sent. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the functions should try to send pdu till it returns. + * @return >0 Number of Bytes sent. + * @return TR_ERROR On Error. + * @return TR_WOULDBLOCK If send would block. + */ +int tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); + +/** + * Repeatedly calls tr_recv(..) till len Bytes were received, the timeout expired or an error occurred. + * @param[in] socket Socket that will be used. + * @param[out] buf Received data, must be an allocated memory area of >=len bytes. + * @param[in] len Size of pdu in Bytes. + * @param[in] timeout Max. seconds the functions should try to receive len data till it returns. + * @return >0 Number of Bytes received. + * @return TR_ERROR On error. + * @return TR_WOULDBLOCK If send would block. + */ +int tr_recv_all(const struct tr_socket *socket, const void *buf, const size_t len, const time_t timeout); + +/** + * Returns an identifier for the socket endpoint, eg host:port. + * @param[in] socket + * return Pointer to a \0 terminated String + * return NULL on error + */ +const char *tr_ident(struct tr_socket *socket); + +#endif +/** @} */ diff --git a/scripts/check-coding-style.sh b/scripts/check-coding-style.sh new file mode 100755 index 0000000..5f1e3c3 --- /dev/null +++ b/scripts/check-coding-style.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# ---HELP--- +# to check kernel coding style of file(s): +# a) either pass a filename as cmdline parameter +# b) run without cmdline parameters and check all non third-party code +# ---HELP--- + +READLINK=$(which greadlink) +[ -z "$READLINK" ] && { + READLINK=$(which readlink) +} +SCRIPT_DIR=$(dirname "$($READLINK -f "$0")") +SCRIPT_FILE="$SCRIPT_DIR/check-coding-files.txt" +SOURCE_DIR_NAMES="rtrlib tools tests" +EXIT_CODE=0 +if [ -z "$1" ] ; then + for dir in ${SOURCE_DIR_NAMES}; do + normalized_dir=$($READLINK -f "${SCRIPT_DIR}/../${dir}") + CHECKSOURCE+=" $(find ${normalized_dir} -name '*.c' -or -name '*.h')" + done +else + CHECKSOURCE=$($READLINK -f "$1") +fi +cd $SCRIPT_DIR/.. +for i in $CHECKSOURCE; do + echo "> check coding style of $i ..." + IGNORE="PREFER_KERNEL_TYPES,CONST_STRUCT,OPEN_BRACE,SPDX_LICENSE_TAG,OPEN_ENDED_LINE,UNNECESSARY_PARENTHESES,PREFER_PRINTF,GLOBAL_INITIALISERS,PREFER_PACKED,BOOL_MEMBER,STATIC_CONST_CHAR_ARRAY,LONG_LINE_STRING" + if [[ $i == *"unittest"* ]]; then + IGNORE="${IGNORE},CAMELCASE" + fi + $SCRIPT_DIR/checkpatch.pl -f --strict --no-tree --terse --show-types \ + --max-line-length 120 --ignore ${IGNORE} $i + + if [ $? -ne "0" ]; then + EXIT_CODE=1 + fi +done + +exit $EXIT_CODE diff --git a/scripts/check-exports.sh b/scripts/check-exports.sh new file mode 100755 index 0000000..2b7dcad --- /dev/null +++ b/scripts/check-exports.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# ---HELP--- +# check if exported symbols match up with header content and the shared object symbol table +# Must be run from rtrlibs root dir. +# ---HELP--- + +SO_SYMBOL_WHITELIST=(__bss_start _edata _end _fini _init __gcov_master __gcov_sort_n_vals __gcov_var) + +ERROR=0 + +# Functions annoted with RTRLIB_EXPORT +EXPORTS=$( + ctags -x --c-kinds=fp $(find rtrlib -iname '*.c' ! -name '*tommy*') | + grep 'RTRLIB_EXPORT' | awk '{ print $1 }' | + sort) + +# Functions found in public headers +HEADER_SYMBOLS=$( + ctags -x --c-kinds=fp $(find rtrlib -iname '*.h' -type f ! -name '*_private.h' ! -name '*tommy*') | + awk '{ print $1 }' | + sort) + +# Symbols found in librtrs dynamic export table +SO_SYMBOLS=$(nm -g --defined-only librtr.so | awk '{ print $3 }' | sort) + +# Filter known false positives in dynamic symbol table +for symbol in "${SO_SYMBOL_WHITELIST[@]}"; do + SO_SYMBOLS=$(echo "${SO_SYMBOLS}" | grep -v "^${symbol}\$") +done + +# check for equality of $EXPORTS AND $HEADER_SYMBOLS +diff -q <(echo "$EXPORTS") <(echo "$HEADER_SYMBOLS") > /dev/null +if [[ $? -ne 0 ]]; then + echo "Functions annotated for export are not equal to functions found in public header!" + comm <(echo "$EXPORTS") <(echo "$HEADER_SYMBOLS") + ERROR=1 +fi + +echo + +# check for equality of $HEADER_SYMBOLS and $SO_SYMBOLS +diff -q <(echo "$HEADER_SYMBOLS") <(echo "$SO_SYMBOLS") > /dev/null +if [[ $? -ne 0 ]]; then + echo "Functions found in public header are not equal to exported functions!" + comm <(echo "$HEADER_SYMBOLS") <(echo "$SO_SYMBOLS") + ERROR=1 +fi + + +exit $ERROR diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl new file mode 100755 index 0000000..b737ca9 --- /dev/null +++ b/scripts/checkpatch.pl @@ -0,0 +1,6720 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit) +# (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft <apw@canonical.com> +# (c) 2010-2018 Joe Perches <joe@perches.com> + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $tree = 1; +my $chk_signoff = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 80; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $typedefsfile = ""; +my $color = "auto"; +my $allow_c99_comments = 1; + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + <rev> + <rev>^ + <rev>~n + multiple git commits with: + <rev1>..<rev2> + <rev1>...<rev2> + <rev>-<count> + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, if exceeded, warn + --min-conf-desc-length=n set the min description length, if shorter, warn + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + "<inputfile>.EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:/usr/share/codespell/dictionary.txt) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my @types = (); + # Also catch when type or level is passed through a variable + for ($text =~ /(?:(?:\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + push (@types, $_); + } + @types = sort(uniq(@types)); + print("#\tMessage type\n\n"); + foreach my $type (@types) { + print(++$count . "\t" . $type . "\n"); + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'h|help' => \$help, + 'version' => \$help +) or help(1); + +help(0) if ($help); + +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "Invalid color mode: $color\n"; +} + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?<!-)>}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "synchronize_rcu_bh" => "synchronize_rcu", + "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", + "call_rcu_bh" => "call_rcu", + "rcu_barrier_bh" => "rcu_barrier", + "synchronize_sched" => "synchronize_rcu", + "synchronize_sched_expedited" => "synchronize_rcu_expedited", + "call_rcu_sched" => "call_rcu", + "rcu_barrier_sched" => "rcu_barrier", + "get_state_synchronize_sched" => "get_state_synchronize_rcu", + "cond_synchronize_sched" => "cond_synchronize_rcu", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if ($$wordsRef ne ""); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs = ""; +read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; + +my $typeOtherTypedefs = ""; +if (length($typedefsfile)) { + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; +} +$typeTypedefs .= '|' . $typeOtherTypedefs if ($typeOtherTypedefs ne ""); + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:$Storage\s+)?${Type}\s+uninitialized_var\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\( +)}; + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + my $status = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + + return $status =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python") eq "" || !(-e "$root/scripts/spdxcheck.py") || !(-e "$root/.git")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | python scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e ".git") { + my $git_last_include_commit = `git log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e ".git") { + $files = `git ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e ".git")); + + my $output = `git log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous\./) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e ".git"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `git log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +for my $filename (@ARGV) { + my $FILE; + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith <joe@smith.com" bad + if ($name ne "" && $address !~ /^<[^>]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + $name = trim($name); + $name =~ s/^\"|\"$//g; + $address = trim($address); + $address =~ s/^\<|\>$//g; + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?<!\\)"/\\"/g; ##escape quotes + $name = "\"$name\""; + } + + return ($name, $address, $comment); +} + +sub format_email { + my ($name, $address) = @_; + + my $formatted_email; + + $name = trim($name); + $name =~ s/^\"|\"$//g; + $address = trim($address); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?<!\\)"/\\"/g; ##escape quotes + $name = "\"$name\""; + } + + if ("$name" eq "") { + $formatted_email = "$address"; + } else { + $formatted_email = "$name <$address>"; + } + + return $formatted_email; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % 8) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # Catch a comment on the end of the line itself. + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + $output = (split('\n', $output))[0] . "\n" if ($terse); + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = 8; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $author = ''; + my $authorsignoff = 0; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.rst$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.txt\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/[\w/]+@ && + $line =~ m@^\s+diff\b.*a/([\w/]+)\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '') { + my $l = $line; + $l =~ s/"//g; + if ($l =~ /^\s*signed-off-by:\s*\Q$author\E/i) { + $authorsignoff = 1; + } + } + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $email_address)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" </ </; + # Don't force email to have quotes + # Allow just an angle bracketed address + if ("$dequoted$comment" ne $email && + "<$email_address>$comment" ne $email && + "$suggested_email$comment" ne $email) { + WARN("BAD_SIGN_OFF", + "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr); + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for unwanted Gerrit info + if ($in_commit_log && $line =~ /^\s*change-id:/i) { + ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream.\n" . $herecurr); + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/)) { + # stack dump address + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|Link:)/i || + # A Fixes: or Link: line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for git id commit length and improperly formed commit descriptions + if ($in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + ($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $hasdesc = 0; + my $hasparens = 0; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + + if ($line =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + } elsif ($line =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + $short = 0 if ($line =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($line =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($line =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($line =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + if ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)"\)/i) { + $orig_desc = $1; + $hasparens = 1; + } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s*$/i && + defined $rawlines[$linenr] && + $rawlines[$linenr] =~ /^\s*\("([^"]+)"\)/) { + $orig_desc = $1; + $hasparens = 1; + } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("[^"]+$/i && + defined $rawlines[$linenr] && + $rawlines[$linenr] =~ /^\s*[^"]+"\)/) { + $line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)$/i; + $orig_desc = $1; + $rawlines[$linenr] =~ /^\s*([^"]+)"\)/; + $orig_desc .= " " . $1; + $hasparens = 1; + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$hasparens)) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herecurr); + } + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + while ($rawline =~ /(?:^|[^a-z@])($misspellings)(?:\b|$|[^a-z@])/gi) { + my $typo = $1; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $length = 0; + my $cnt = $realcnt; + my $ln = $linenr + 1; + my $f; + my $is_start = 0; + my $is_end = 0; + for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) { + $f = $lines[$ln - 1]; + $cnt-- if ($lines[$ln - 1] !~ /^-/); + $is_end = $lines[$ln - 1] =~ /^\+/; + + next if ($f =~ /^-/); + last if (!$file && $f =~ /^\@\@/); + + if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $is_start = 1; + } elsif ($lines[$ln - 1] =~ /^\+\s*(?:help|---help---)\s*$/) { + if ($lines[$ln - 1] =~ "---help---") { + WARN("CONFIG_DESCRIPTION", + "prefer 'help' over '---help---' for new help texts\n" . $herecurr); + } + $length = -1; + } + + $f =~ s/^.//; + $f =~ s/#.*//; + $f =~ s/^\s+//; + next if ($f =~ /^$/); + + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + $is_end = 1; + last; + } + $length++; + } + if ($is_start && $is_end && $length < $min_conf_desc_length) { + WARN("CONFIG_DESCRIPTION", + "please write a paragraph that describes the config symbol fully\n" . $herecurr); + } + #print "is_start<$is_start> is_end<$is_end> length<$length>\n"; + } + +# check for MAINTAINERS entries that don't have the right form + if ($realfile =~ /^MAINTAINERS$/ && + $rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } + +# discourage the use of boolean for type definition attributes of Kconfig options + if ($realfile =~ /Kconfig/ && + $line =~ /^\+\s*\bboolean\b/) { + WARN("CONFIG_TYPE_BOOLEAN", + "Use of boolean is deprecated, please use bool instead.\n" . $herecurr); + } + + if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && + ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { + my $flag = $1; + my $replacement = { + 'EXTRA_AFLAGS' => 'asflags-y', + 'EXTRA_CFLAGS' => 'ccflags-y', + 'EXTRA_CPPFLAGS' => 'cppflags-y', + 'EXTRA_LDFLAGS' => 'ldflags-y', + }; + + WARN("DEPRECATED_VARIABLE", + "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.txt"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "^$vendor\\b" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + } elsif ($realfile =~ /\.(c|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + + if ($comment !~ /^$/ && + $rawline !~ /^\+\Q$comment\E SPDX-License-Identifier: /) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + } + } + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts)$/); + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + (show_type("LONG_LINE") || show_type($msg_type))) { + WARN($msg_type, + "line over $max_line_length characters\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than 8 must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {8,8}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev); + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev); + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % 8) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/8)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / 8) . + " " x ($pos % 8); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comment styles +# Networking with an initial /* + if ($realfile =~ m@^(drivers/net/|net/)@ && + $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && + $rawline =~ /^\+[ \t]*\*/ && + $realline > 2) { + WARN("NETWORKING_BLOCK_COMMENT_STYLE", + "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*EXPORT_SYMBOL/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations + if ($sline =~ /^\+\s+\S/ && #Not at char 1 + # actual declarations + ($prevline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $prevline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $prevline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $prevline =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($prevline =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $prevline =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $prevline =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sline =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sline =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sline =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sline =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sline =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/) && + # indentation of previous and current line are the same + (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto or return and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(?:goto|return)\b/) { + WARN("UNNECESSARY_BREAK", + "break is not useful after a goto or return\n" . $hereprev); + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % 8) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + 8))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ || + $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ || + $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s+$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# avoid BUG() or BUG_ON() + if ($line =~ /\b(?:BUG|BUG_ON)\b/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + + if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to printk(KERN_$orig ...\n" . $herecurr); + } + + if ($line =~ /\bpr_warning\s*\(/) { + if (WARN("PREFER_PR_LEVEL", + "Prefer pr_warn(... to pr_warning(...\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\bpr_warning\b/pr_warn/; + } + } + + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*){(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsly report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons in if uses +# when !drivers/staging or command-line uses --strict + if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && + $perl_version_ok && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +#goto labels aren't indented, allow a single space however + if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and + !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR') { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr); + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real); + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore SI style variants like nS, mV and dB (ie: max_uV, regulator_min_uA_show) + $var !~ /^(?:[a-z_]*?)_?[a-z][A-Z](?:_[a-z_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# multi-statement macros should be enclosed in a do while loop, grab the +# first statement and ensure its the whole macro if its not enclosed +# in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1/ || + $dstat =~ s/\{[^\{\}]*\}/1/ || + $dstat =~ s/.\[[^\[\]]*\]/1/) + { + } + + # Flatten any obvious string concatentation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Za-z0-9_]/ || $line =~ /[A-Za-z0-9_]$String/) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*(?:devm_)?(?:[kv][czm]alloc(?:_node|_array)?\b|kstrdup|kmemdup|(?:dev_)?alloc_skb)/) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb| + read_barrier_depends + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for smp_read_barrier_depends and read_barrier_depends + if (!$file && $line =~ /\b(smp_|)read_barrier_depends\s*\(/) { + WARN("READ_BARRIER_DEPENDS", + "$1read_barrier_depends should only be used in READ_ONCE or DEC Alpha code\n" . $herecurr); + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for __attribute__ packed, prefer __packed + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) { + WARN("PREFER_PACKED", + "__packed is preferred over __attribute__((packed))\n" . $herecurr); + } + +# Check for __attribute__ aligned, prefer __aligned + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(.*aligned/) { + WARN("PREFER_ALIGNED", + "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr); + } + +# Check for __attribute__ format(printf, prefer __printf + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) { + if (WARN("PREFER_PRINTF", + "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf\s*,\s*(.*)\)\s*\)\s*\)/"__printf(" . trim($1) . ")"/ex; + + } + } + +# Check for __attribute__ format(scanf, prefer __scanf + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) { + if (WARN("PREFER_SCANF", + "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\s*,\s*(.*)\)\s*\)\s*\)/"__scanf(" . trim($1) . ")"/ex; + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant\n" . $herecurr) && + $fix) { + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w))/g) { + $specifier = $1; + $extension = $2; + if ($extension !~ /[SsBKRraEhMmIiUDdgVCbGNOx]/) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $ext_type = "Deprecated"; + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } + + WARN("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/ && + $function_name ne 'uninitialized_var') + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.rst\n" . $herecurr); + } + } + +# check for pointless casting of kmalloc return + if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*([kv][mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_array"; + $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b(kcalloc|kmalloc_array)\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(CONFIG_[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to CONFIG_<FOO> || CONFIG_<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for case / default statements not preceded by break/fallthrough/switch + if ($line =~ /^.\s*(?:case\s+(?:$Ident|$Constant)\s*|default):/) { + my $has_break = 0; + my $has_statement = 0; + my $count = 0; + my $prevline = $linenr; + while ($prevline > 1 && ($file || $count < 3) && !$has_break) { + $prevline--; + my $rline = $rawlines[$prevline - 1]; + my $fline = $lines[$prevline - 1]; + last if ($fline =~ /^\@\@/); + next if ($fline =~ /^\-/); + next if ($fline =~ /^.(?:\s*(?:case\s+(?:$Ident|$Constant)[\s$;]*|default):[\s$;]*)*$/); + $has_break = 1 if ($rline =~ /fall[\s_-]*(through|thru)/i); + next if ($fline =~ /^.[\s$;]*$/); + $has_statement = 1; + $count++; + $has_break = 1 if ($fline =~ /\bswitch\b|\b(?:break\s*;[\s$;]*$|exit\s*\(\b|return\b|goto\b|continue\b)/); + } + if (!$has_break && $has_statement) { + WARN("MISSING_BREAK", + "Possible switch case/default not preceded by break or fallthrough comment\n" . $herecurr); + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for bool bitfields + if ($sline =~ /^.\s+bool\s*$Ident\s*:\s*\d+\s*;/) { + WARN("BOOL_BITFIELD", + "Avoid using bool as bitfield. Prefer bool bitfields as unsigned int or u<8|16|32>\n" . $herecurr); + } + +# check for bool use in .h files + if ($realfile =~ /\.h$/ && + $sline =~ /^.\s+bool\s*$Ident\s*(?::\s*d+\s*)?;/) { + CHK("BOOL_MEMBER", + "Avoid using bool structure members because of possible alignment issues - see: https://lkml.org/lkml/2017/11/21/384\n" . $herecurr); + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if ($line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# check for mutex_trylock_recursive usage + if ($line =~ /mutex_trylock_recursive/) { + ERROR("LOCKING", + "recursive locking is bad, do not use this ever.\n" . $herecurr); + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif (!$authorsignoff) { + WARN("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/scripts/const_structs.checkpatch b/scripts/const_structs.checkpatch new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/scripts/const_structs.checkpatch @@ -0,0 +1 @@ + diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh new file mode 100755 index 0000000..ac77f85 --- /dev/null +++ b/scripts/cppcheck.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cppcheck --error-exitcode=1 --enable=all --inline-suppr \ + --template='{id}:{file}({line}):({severity}) {message}' \ + -i rtrlib/spki/hashtable/tommyds-2.2/ tools/ rtrlib/ diff --git a/scripts/spelling.txt b/scripts/spelling.txt new file mode 100644 index 0000000..946caf3 --- /dev/null +++ b/scripts/spelling.txt @@ -0,0 +1,1072 @@ +# Originally from Debian's Lintian tool. Various false positives have been +# removed, and various additions have been made as they've been discovered +# in the kernel source. +# +# License: GPLv2 +# +# The format of each line is: +# mistake||correction +# +abandonning||abandoning +abigious||ambiguous +abitrate||arbitrate +abov||above +abreviated||abbreviated +absense||absence +absolut||absolute +absoulte||absolute +acccess||access +acceleratoin||acceleration +accelleration||acceleration +accesing||accessing +accesnt||accent +accessable||accessible +accesss||access +accidentaly||accidentally +accidentually||accidentally +accoding||according +accomodate||accommodate +accomodates||accommodates +accordign||according +accoring||according +accout||account +accquire||acquire +accquired||acquired +accross||across +acessable||accessible +acess||access +achitecture||architecture +acient||ancient +acitions||actions +acitve||active +acknowldegement||acknowldegement +acknowledgement||acknowledgment +ackowledge||acknowledge +ackowledged||acknowledged +acording||according +activete||activate +acumulating||accumulating +adapater||adapter +addional||additional +additionaly||additionally +addres||address +addreses||addresses +addresss||address +aditional||additional +aditionally||additionally +aditionaly||additionally +adminstrative||administrative +adress||address +adresses||addresses +adviced||advised +afecting||affecting +agaist||against +albumns||albums +alegorical||allegorical +algorith||algorithm +algorithmical||algorithmically +algoritm||algorithm +algoritms||algorithms +algorrithm||algorithm +algorritm||algorithm +allign||align +allocatrd||allocated +allocte||allocate +allpication||application +alocate||allocate +alogirhtms||algorithms +alogrithm||algorithm +alot||a lot +alow||allow +alows||allows +altough||although +alue||value +ambigious||ambiguous +amoung||among +amout||amount +analysator||analyzer +ang||and +anniversery||anniversary +annoucement||announcement +anomolies||anomalies +anomoly||anomaly +anway||anyway +aplication||application +appearence||appearance +applicaion||application +appliction||application +applictions||applications +appplications||applications +appropiate||appropriate +appropriatly||appropriately +approriate||appropriate +approriately||appropriately +apropriate||appropriate +aquainted||acquainted +aquired||acquired +aquisition||acquisition +arbitary||arbitrary +architechture||architecture +arguement||argument +arguements||arguments +aritmetic||arithmetic +arne't||aren't +arraival||arrival +artifical||artificial +artillary||artillery +asign||assign +assertation||assertion +assiged||assigned +assigment||assignment +assigments||assignments +assistent||assistant +assocation||association +associcated||associated +assotiated||associated +assum||assume +assumtpion||assumption +asuming||assuming +asycronous||asynchronous +asynchnous||asynchronous +atomatically||automatically +atomicly||atomically +attachement||attachment +attched||attached +attemps||attempts +attruibutes||attributes +authentification||authentication +automaticaly||automatically +automaticly||automatically +automatize||automate +automatized||automated +automatizes||automates +autonymous||autonomous +auxillary||auxiliary +auxilliary||auxiliary +avaiable||available +avaible||available +availabe||available +availabled||available +availablity||availability +availale||available +availavility||availability +availble||available +availiable||available +avalable||available +avaliable||available +aysnc||async +backgroud||background +backword||backward +backwords||backwards +bahavior||behavior +bakup||backup +baloon||balloon +baloons||balloons +bandwith||bandwidth +batery||battery +beacuse||because +becasue||because +becomming||becoming +becuase||because +beeing||being +befor||before +begining||beginning +beter||better +betweeen||between +bianries||binaries +bitmast||bitmask +boardcast||broadcast +borad||board +boundry||boundary +brievely||briefly +broadcat||broadcast +cacluated||calculated +caculation||calculation +calender||calendar +calle||called +calucate||calculate +calulate||calculate +cancelation||cancellation +capabilites||capabilities +capabitilies||capabilities +capatibilities||capabilities +carefuly||carefully +cariage||carriage +catagory||category +cehck||check +challange||challenge +challanges||challenges +chanell||channel +changable||changeable +channle||channel +channnel||channel +charachter||character +charachters||characters +charactor||character +charater||character +charaters||characters +charcter||character +chcek||check +chck||check +checksuming||checksumming +childern||children +childs||children +chiled||child +chked||checked +chnage||change +chnages||changes +chnnel||channel +choosen||chosen +chouse||chose +circumvernt||circumvent +claread||cleared +clared||cleared +closeing||closing +clustred||clustered +collapsable||collapsible +colorfull||colorful +comand||command +comit||commit +commerical||commercial +comming||coming +comminucation||communication +commited||committed +commiting||committing +committ||commit +commoditiy||commodity +compability||compatibility +compaibility||compatibility +compatability||compatibility +compatable||compatible +compatibiliy||compatibility +compatibilty||compatibility +compatiblity||compatibility +competion||completion +compilant||compliant +compleatly||completely +completly||completely +complient||compliant +componnents||components +compres||compress +compresion||compression +comression||compression +comunication||communication +conbination||combination +conditionaly||conditionally +conected||connected +configuratoin||configuration +configuraton||configuration +configuretion||configuration +conider||consider +conjuction||conjunction +connectinos||connections +connnection||connection +connnections||connections +consistancy||consistency +consistant||consistent +containes||contains +containts||contains +contaisn||contains +contant||contact +contence||contents +continous||continuous +continously||continuously +continueing||continuing +contraints||constraints +controled||controlled +controler||controller +controll||control +contruction||construction +contry||country +convertion||conversion +convertor||converter +convienient||convenient +convinient||convenient +corected||corrected +correponding||corresponding +correponds||corresponds +correspoding||corresponding +cotrol||control +couter||counter +coutner||counter +cryptocraphic||cryptographic +cunter||counter +curently||currently +dafault||default +deafult||default +deamon||daemon +decompres||decompress +decription||description +defailt||default +defferred||deferred +definate||definite +definately||definitely +defintion||definition +defintions||definitions +defualt||default +defult||default +deivce||device +delared||declared +delare||declare +delares||declares +delaring||declaring +delemiter||delimiter +dependancies||dependencies +dependancy||dependency +dependant||dependent +depreacted||deprecated +depreacte||deprecate +desactivate||deactivate +desciptors||descriptors +descripton||description +descrition||description +descritptor||descriptor +desctiptor||descriptor +desriptor||descriptor +desriptors||descriptors +destory||destroy +destoryed||destroyed +destorys||destroys +destroied||destroyed +detabase||database +develope||develop +developement||development +developped||developed +developpement||development +developper||developer +developpment||development +deveolpment||development +devided||divided +deviece||device +diable||disable +dictionnary||dictionary +didnt||didn't +diferent||different +differrence||difference +difinition||definition +diplay||display +direectly||directly +disapear||disappear +disapeared||disappeared +disappared||disappeared +disconnet||disconnect +discontinous||discontinuous +dispertion||dispersion +dissapears||disappears +distiction||distinction +docuentation||documentation +documantation||documentation +documentaion||documentation +documment||document +doesnt||doesn't +dorp||drop +dosen||doesn +downlad||download +downlads||downloads +druing||during +dynmaic||dynamic +easilly||easily +ecspecially||especially +edditable||editable +editting||editing +efficently||efficiently +ehther||ether +eigth||eight +eletronic||electronic +enabledi||enabled +enchanced||enhanced +encorporating||incorporating +encrupted||encrypted +encrypiton||encryption +endianess||endianness +enhaced||enhanced +enlightnment||enlightenment +enocded||encoded +enterily||entirely +enviroiment||environment +enviroment||environment +environement||environment +environent||environment +eqivalent||equivalent +equiped||equipped +equivelant||equivalent +equivilant||equivalent +eror||error +estbalishment||establishment +etsablishment||establishment +etsbalishment||establishment +excecutable||executable +exceded||exceeded +excellant||excellent +existance||existence +existant||existent +exixt||exist +exlcude||exclude +exlcusive||exclusive +exmaple||example +expecially||especially +explicite||explicit +explicitely||explicitly +explict||explicit +explictly||explicitly +expresion||expression +exprimental||experimental +extened||extended +extensability||extensibility +extention||extension +extracter||extractor +faild||failed +faill||fail +failue||failure +failuer||failure +faireness||fairness +faliure||failure +familar||familiar +fatser||faster +feauture||feature +feautures||features +fetaure||feature +fetaures||features +fileystem||filesystem +finanize||finalize +findn||find +finilizes||finalizes +finsih||finish +flusing||flushing +folloing||following +followign||following +follwing||following +forseeable||foreseeable +forse||force +fortan||fortran +forwardig||forwarding +framwork||framework +frequncy||frequency +frome||from +fucntion||function +fuction||function +fuctions||functions +funcion||function +functionallity||functionality +functionaly||functionally +functionnality||functionality +functonality||functionality +funtion||function +funtions||functions +furthur||further +futhermore||furthermore +futrue||future +gaurenteed||guaranteed +generiously||generously +genric||generic +globel||global +grabing||grabbing +grahical||graphical +grahpical||graphical +grapic||graphic +guage||gauge +guarenteed||guaranteed +guarentee||guarantee +halfs||halves +hander||handler +handfull||handful +hanled||handled +happend||happened +harware||hardware +heirarchically||hierarchically +helpfull||helpful +hierachy||hierarchy +hierarchie||hierarchy +howver||however +hsould||should +hypter||hyper +identidier||identifier +imblance||imbalance +immeadiately||immediately +immedaite||immediate +immediatelly||immediately +immediatly||immediately +immidiate||immediate +impelentation||implementation +impementated||implemented +implemantation||implementation +implemenation||implementation +implementaiton||implementation +implementated||implemented +implemention||implementation +implemetation||implementation +implemntation||implementation +implentation||implementation +implmentation||implementation +implmenting||implementing +incomming||incoming +incompatabilities||incompatibilities +incompatable||incompatible +inconsistant||inconsistent +increas||increase +incrment||increment +indendation||indentation +indended||intended +independant||independent +independantly||independently +independed||independent +indiate||indicate +inexpect||inexpected +infomation||information +informatiom||information +informations||information +informtion||information +infromation||information +ingore||ignore +inital||initial +initalised||initialized +initalise||initialize +initalize||initialize +initation||initiation +initators||initiators +initializiation||initialization +initialzed||initialized +initilization||initialization +initilize||initialize +inofficial||unofficial +insititute||institute +instal||install +inteface||interface +integreated||integrated +integrety||integrity +integrey||integrity +intendet||intended +intented||intended +interanl||internal +interchangable||interchangeable +interferring||interfering +interger||integer +intermittant||intermittent +internel||internal +interoprability||interoperability +interrface||interface +interrrupt||interrupt +interrup||interrupt +interrups||interrupts +interruptted||interrupted +interupted||interrupted +interupt||interrupt +intial||initial +intialized||initialized +intialize||initialize +intregral||integral +intrrupt||interrupt +intuative||intuitive +invaid||invalid +invalde||invald +invalide||invalid +invididual||individual +invokation||invocation +invokations||invocations +irrelevent||irrelevant +isnt||isn't +isssue||issue +itslef||itself +jave||java +jeffies||jiffies +juse||just +jus||just +kown||known +langage||language +langauage||language +langauge||language +langugage||language +lauch||launch +layed||laid +leightweight||lightweight +lengh||length +lenght||length +lenth||length +lesstiff||lesstif +libaries||libraries +libary||library +librairies||libraries +libraris||libraries +licenceing||licencing +loggging||logging +loggin||login +logile||logfile +loosing||losing +losted||lost +machinary||machinery +maintainance||maintenance +maintainence||maintenance +maintan||maintain +makeing||making +malplaced||misplaced +malplace||misplace +managable||manageable +managment||management +mangement||management +manoeuvering||maneuvering +mappping||mapping +mathimatical||mathematical +mathimatic||mathematic +mathimatics||mathematics +maxium||maximum +mechamism||mechanism +meetign||meeting +ment||meant +mergable||mergeable +mesage||message +messags||messages +messgaes||messages +messsage||message +messsages||messages +microprocesspr||microprocessor +milliseonds||milliseconds +minumum||minimum +miscelleneous||miscellaneous +misformed||malformed +mispelled||misspelled +mispelt||misspelt +miximum||maximum +mmnemonic||mnemonic +mnay||many +modeled||modelled +modulues||modules +monochorome||monochrome +monochromo||monochrome +monocrome||monochrome +mopdule||module +mroe||more +mulitplied||multiplied +multidimensionnal||multidimensional +multple||multiple +mumber||number +muticast||multicast +mutiple||multiple +mutli||multi +nams||names +navagating||navigating +nead||need +neccecary||necessary +neccesary||necessary +neccessary||necessary +necesary||necessary +negaive||negative +negoitation||negotiation +negotation||negotiation +nerver||never +nescessary||necessary +nessessary||necessary +noticable||noticeable +notications||notifications +notifed||notified +numebr||number +numner||number +obtaion||obtain +occassionally||occasionally +occationally||occasionally +occurance||occurrence +occurances||occurrences +occured||occurred +occurence||occurrence +occure||occurred +occuring||occurring +offet||offset +omitt||omit +ommiting||omitting +ommitted||omitted +onself||oneself +ony||only +operatione||operation +opertaions||operations +optionnal||optional +optmizations||optimizations +orientatied||orientated +orientied||oriented +otherise||otherwise +ouput||output +overaall||overall +overhread||overhead +overlaping||overlapping +overriden||overridden +overun||overrun +pacakge||package +pachage||package +packacge||package +packege||package +packge||package +packtes||packets +pakage||package +pallette||palette +paln||plan +paramameters||parameters +paramater||parameter +parametes||parameters +parametised||parametrised +paramter||parameter +paramters||parameters +particuarly||particularly +particularily||particularly +pased||passed +passin||passing +pathes||paths +pecularities||peculiarities +peformance||performance +peice||piece +pendantic||pedantic +peprocessor||preprocessor +perfoming||performing +permissons||permissions +peroid||period +persistance||persistence +persistant||persistent +platfrom||platform +plattform||platform +pleaes||please +ploting||plotting +plugable||pluggable +poinnter||pointer +poiter||pointer +posible||possible +positon||position +possibilites||possibilities +powerfull||powerful +preceeded||preceded +preceeding||preceding +preceed||precede +precendence||precedence +precission||precision +preemptable||preemptible +prefered||preferred +prefferably||preferably +premption||preemption +prepaired||prepared +pressre||pressure +primative||primitive +princliple||principle +priorty||priority +privilaged||privileged +privilage||privilege +priviledge||privilege +priviledges||privileges +probaly||probably +procceed||proceed +proccesors||processors +procesed||processed +proces||process +processessing||processing +processess||processes +processpr||processor +processsed||processed +processsing||processing +procteted||protected +prodecure||procedure +progams||programs +progess||progress +programers||programmers +programm||program +programms||programs +progresss||progress +promiscous||promiscuous +promps||prompts +pronnounced||pronounced +prononciation||pronunciation +pronouce||pronounce +pronunce||pronounce +propery||property +propigate||propagate +propigation||propagation +propogate||propagate +prosess||process +protable||portable +protcol||protocol +protecion||protection +protocoll||protocol +psudo||pseudo +psuedo||pseudo +psychadelic||psychedelic +pwoer||power +quering||querying +raoming||roaming +reasearcher||researcher +reasearchers||researchers +reasearch||research +recepient||recipient +receving||receiving +recieved||received +recieve||receive +reciever||receiver +recieves||receives +recogniced||recognised +recognizeable||recognizable +recommanded||recommended +recyle||recycle +redircet||redirect +redirectrion||redirection +refcounf||refcount +refence||reference +refered||referred +referenace||reference +refering||referring +refernces||references +refernnce||reference +refrence||reference +registerd||registered +registeresd||registered +registes||registers +registraration||registration +regster||register +regualar||regular +reguator||regulator +regulamentations||regulations +reigstration||registration +releated||related +relevent||relevant +remoote||remote +remore||remote +removeable||removable +repectively||respectively +replacable||replaceable +replacments||replacements +replys||replies +reponse||response +representaion||representation +reqeust||request +requiere||require +requirment||requirement +requred||required +requried||required +requst||request +reseting||resetting +resizeable||resizable +resouces||resources +resoures||resources +responce||response +ressizes||resizes +ressource||resource +ressources||resources +retransmited||retransmitted +retreived||retrieved +retreive||retrieve +retrive||retrieve +retuned||returned +reudce||reduce +reuest||request +reuqest||request +reutnred||returned +rmeoved||removed +rmeove||remove +rmeoves||removes +rountine||routine +routins||routines +rquest||request +runing||running +runned||ran +runnning||running +runtine||runtime +sacrifying||sacrificing +safly||safely +safty||safety +savable||saveable +scaned||scanned +scaning||scanning +scarch||search +seach||search +searchs||searches +secquence||sequence +secund||second +segement||segment +senarios||scenarios +sentivite||sensitive +separatly||separately +sepcify||specify +sepc||spec +seperated||separated +seperately||separately +seperate||separate +seperatly||separately +seperator||separator +sepperate||separate +sequece||sequence +sequencial||sequential +serveral||several +setts||sets +settting||setting +shotdown||shutdown +shoud||should +shouldnt||shouldn't +shoule||should +shrinked||shrunk +siginificantly||significantly +signabl||signal +similary||similarly +similiar||similar +simlar||similar +simliar||similar +simpified||simplified +singaled||signaled +singal||signal +singed||signed +sleeped||slept +softwares||software +speach||speech +specfic||specific +speciefied||specified +specifc||specific +specifed||specified +specificatin||specification +specificaton||specification +specifing||specifying +specifiying||specifying +speficied||specified +speicify||specify +speling||spelling +spinlcok||spinlock +spinock||spinlock +splitted||split +spreaded||spread +sructure||structure +stablilization||stabilization +staically||statically +staion||station +standardss||standards +standartization||standardization +standart||standard +staticly||statically +stoped||stopped +stoppped||stopped +straming||streaming +struc||struct +structres||structures +stuct||struct +stucture||structure +sturcture||structure +subdirectoires||subdirectories +suble||subtle +substract||subtract +succesfully||successfully +succesful||successful +successfull||successful +sucessfully||successfully +sucess||success +superflous||superfluous +superseeded||superseded +suplied||supplied +suported||supported +suport||support +suppored||supported +supportin||supporting +suppoted||supported +suppported||supported +suppport||support +supress||suppress +surpresses||suppresses +susbsystem||subsystem +suspicously||suspiciously +swaping||swapping +switchs||switches +symetric||symmetric +synax||syntax +synchonized||synchronized +syncronize||synchronize +syncronizing||synchronizing +syncronus||synchronous +syste||system +sytem||system +sythesis||synthesis +taht||that +targetted||targeted +targetting||targeting +teh||the +temorary||temporary +temproarily||temporarily +thier||their +threds||threads +threshhold||threshold +throught||through +thses||these +tiggered||triggered +tipically||typically +tmis||this +torerable||tolerable +tramsmitted||transmitted +tramsmit||transmit +tranfer||transfer +transciever||transceiver +transferd||transferrd +transfered||transferred +transfering||transferring +transision||transition +transmittd||transmitted +transormed||transformed +trasmission||transmission +treshold||threshold +trigerring||triggering +trun||turn +ture||true +tyep||type +udpate||update +uesd||used +unconditionaly||unconditionally +underun||underrun +unecessary||unnecessary +unexecpted||unexpected +unexpectd||unexpected +unexpeted||unexpected +unfortunatelly||unfortunately +unifiy||unify +unintialized||uninitialized +unknonw||unknown +unknow||unknown +unkown||unknown +unneedingly||unnecessarily +unresgister||unregister +unsinged||unsigned +unstabel||unstable +unsuccessfull||unsuccessful +unsuported||unsupported +untill||until +unuseful||useless +upate||update +usefule||useful +usefull||useful +usege||usage +usera||users +usualy||usually +utilites||utilities +utillities||utilities +utilties||utilities +utiltity||utility +utitity||utility +utitlty||utility +vaid||valid +vaild||valid +valide||valid +variantions||variations +varient||variant +vaule||value +verbse||verbose +verisons||versions +verison||version +verson||version +vicefersa||vice-versa +virtal||virtual +virtaul||virtual +virtiual||virtual +visiters||visitors +vitual||virtual +wating||waiting +wether||whether +whataver||whatever +whcih||which +whenver||whenever +wheter||whether +whe||when +wierd||weird +wiil||will +wirte||write +withing||within +wnat||want +workarould||workaround +writeing||writing +writting||writing +zombe||zombie +zomebie||zombie diff --git a/scripts/travis.sh b/scripts/travis.sh new file mode 100755 index 0000000..02d7752 --- /dev/null +++ b/scripts/travis.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +BUILDSTEP_FAILED=0 + +function run_command { + eval $@ + + ret=$? + if [ $ret != 0 ]; then + colour=$RED + BUILDSTEP_FAILED=1 + else + colour=$GREEN + fi + echo -e "\n${colour}The command \"$@\" exited with $ret.$NC\n\n" + + return $ret +} + +function checkpatch { + git diff $TRAVIS_BRANCH {rtrlib,tools,tests}/**/*.[ch] > /tmp/patch + run_command scripts/checkpatch.pl --ignore FILE_PATH_CHANGES,PREFER_KERNEL_TYPES,CONST_STRUCT,OPEN_BRACE,SPDX_LICENSE_TAG,OPEN_ENDED_LINE,UNNECESSARY_PARENTHESES,PREFER_PRINTF,GLOBAL_INITIALISERS,PREFER_PACKED,BOOL_MEMBER,STATIC_CONST_CHAR_ARRAY,LONG_LINE_STRING --terse --no-tree --strict --show-types --max-line-length 120 /tmp/patch + ret=$? + if [ $ret != 0 ]; then + cat -n /tmp/patch + fi +} + +[[ $TRAVIS = "true" ]] && run_command git fetch --unshallow +run_command scripts/cppcheck.sh +run_command scripts/check-coding-style.sh +[[ $TRAVIS_EVENT_TYPE = "pull_request" ]] && run_command checkpatch +run_command cmake -D RTRLIB_TRANSPORT_SSH=Off . +run_command make +run_command make test +run_command make clean +run_command cmake -D CMAKE_BUILD_TYPE=Release -DENABLE_COVERAGE=On -DUNIT_TESTING=On -DRTRLIB_TRANSPORT_SSH=On . +run_command make +run_command make test +run_command make gcov +run_command scripts/check-exports.sh + +exit $BUILDSTEP_FAILED diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..d4757da --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,35 @@ +set(CMAKE_BUILD_TYPE Debug) + +add_executable(test_pfx test_pfx.c) +target_link_libraries(test_pfx rtrlib_static) +add_coverage(test_pfx) +add_executable(test_trie test_trie.c) +target_link_libraries(test_trie rtrlib_static) +add_coverage(test_trie) +add_executable(test_pfx_locks test_pfx_locks.c) +target_link_libraries(test_pfx_locks rtrlib_static) +add_coverage(test_pfx_locks) +add_executable(test_ht_spkitable test_ht_spkitable.c) +target_link_libraries(test_ht_spkitable rtrlib_static) +add_coverage(test_ht_spkitable) +add_executable(test_ht_spkitable_locks test_ht_spkitable_locks.c) +target_link_libraries(test_ht_spkitable_locks rtrlib_static) +add_coverage(test_ht_spkitable_locks) +add_executable(test_live_validation test_live_validation.c) +target_link_libraries(test_live_validation rtrlib_static) +add_coverage(test_live_validation) +add_executable(test_ipaddr test_ipaddr.c) +target_link_libraries(test_ipaddr rtrlib_static) +add_coverage(test_ipaddr) +add_executable(test_getbits test_getbits.c) +target_link_libraries(test_getbits rtrlib_static) +add_coverage(test_getbits) +add_executable(test_dynamic_groups test_dynamic_groups.c) +target_link_libraries(test_dynamic_groups rtrlib_static) +add_coverage(test_dynamic_groups) + + +if(UNIT_TESTING AND NOT APPLE) + find_package(CMocka REQUIRED) + add_subdirectory(unittests) +endif(UNIT_TESTING AND NOT APPLE) diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c new file mode 100644 index 0000000..51d042c --- /dev/null +++ b/tests/test_dynamic_groups.c @@ -0,0 +1,181 @@ +#include "rtrlib/rtr_mgr_private.h" +#include "rtrlib/rtrlib.h" + +#include "third-party/tommyds/tommylist.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +const int connection_timeout = 20; +enum rtr_mgr_status connection_status = -1; + +static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), + enum rtr_mgr_status status, + const struct rtr_socket *socket __attribute__((unused)), + void *data __attribute__((unused))) +{ + connection_status = status; +} + +int main(void) +{ + //create a TCP transport socket + int retval = 0; + struct tr_socket tr_tcp; + char tcp_host[] = "rpki-validator.realmv6.org"; + char tcp_port[] = "8283"; + + struct tr_tcp_config tcp_config = { + tcp_host, //IP + tcp_port, //Port + NULL, //Source address + NULL, //data + NULL, //new_socket() + 0, // connect timeout + }; + tr_tcp_init(&tcp_config, &tr_tcp); + + struct rtr_socket rtr_tcp; + + rtr_tcp.tr_socket = &tr_tcp; + + struct rtr_mgr_group groups[1]; + + groups[0].sockets = malloc(sizeof(struct rtr_socket *)); + groups[0].sockets_len = 1; + groups[0].sockets[0] = &rtr_tcp; + groups[0].preference = 1; + + struct tr_socket tr_tcp2; + struct rtr_socket rtr_tcp2; + struct rtr_mgr_group group2; + + tr_tcp_init(&tcp_config, &tr_tcp2); + rtr_tcp2.tr_socket = &tr_tcp2; + group2.sockets = malloc(sizeof(struct rtr_socket *)); + group2.sockets_len = 1; + group2.sockets[0] = &rtr_tcp2; + group2.preference = 2; + + struct rtr_mgr_config *conf; + + rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL); + + //start the connection manager + rtr_mgr_start(conf); + + int sleep_counter = 0; + // wait 20 sec till at least one group is fully synchronized with the server + // otherwise EXIT_FAILURE. + while (!rtr_mgr_conf_in_sync(conf)) { + sleep_counter++; + if (connection_status == RTR_MGR_ERROR || sleep_counter > connection_timeout) + return EXIT_FAILURE; + + sleep(1); + } + + assert(conf->len == 1); + + retval = rtr_mgr_add_group(conf, &group2); + assert(retval == RTR_SUCCESS); + + //checking behavior in case the group preference already exists + //by adding the same group twice. + retval = rtr_mgr_add_group(conf, &group2); + assert(retval == RTR_INVALID_PARAM); + + tommy_node *node = tommy_list_head(&conf->groups->list); + struct rtr_mgr_group_node *group_node = node->data; + struct rtr_mgr_group_node *group_node2 = node->next->data; + + assert(group_node->group->preference == 1); + assert(group_node2->group->preference == 2); + assert(conf->len == 2); + + rtr_mgr_remove_group(conf, 1); + + node = tommy_list_head(&conf->groups->list); + group_node = node->data; + assert(group_node->group->preference == 2); + assert(conf->len == 1); + + struct tr_socket tr_tcp3; + struct rtr_socket rtr_tcp3; + struct rtr_mgr_group group3; + + tr_tcp_init(&tcp_config, &tr_tcp3); + rtr_tcp3.tr_socket = &tr_tcp3; + group3.sockets = malloc(sizeof(struct rtr_socket *)); + group3.sockets_len = 1; + group3.sockets[0] = &rtr_tcp3; + group3.preference = 3; + + struct tr_socket tr_tcp4; + struct rtr_socket rtr_tcp4; + struct rtr_mgr_group group4; + + tr_tcp_init(&tcp_config, &tr_tcp4); + rtr_tcp4.tr_socket = &tr_tcp4; + group4.sockets = malloc(sizeof(struct rtr_socket *)); + group4.sockets_len = 1; + group4.sockets[0] = &rtr_tcp4; + group4.preference = 4; + + rtr_mgr_add_group(conf, &group4); + + // remove group 2 so group 4 becomes the active group. + rtr_mgr_remove_group(conf, 2); + + // add group 3 which has a higher preference than group 4 + // and check whether it will be set as the active group. + rtr_mgr_add_group(conf, &group3); + + node = tommy_list_head(&conf->groups->list); + group_node = node->data; + assert(group_node->group->preference == 3); + + //try to remove non-existent group + retval = rtr_mgr_remove_group(conf, 10); + assert(retval == RTR_ERROR); + + struct tr_socket tr_tcp5; + struct rtr_socket rtr_tcp5; + struct rtr_mgr_group group5; + + tr_tcp_init(&tcp_config, &tr_tcp5); + rtr_tcp5.tr_socket = &tr_tcp5; + group5.sockets = malloc(sizeof(struct rtr_socket *)); + group5.sockets_len = 1; + group5.sockets[0] = &rtr_tcp5; + group5.preference = 5; + + //add 100 groups + for (int i = 0; i < 100; i++) { + retval = rtr_mgr_add_group(conf, &group5); + group5.preference++; + assert(retval == RTR_SUCCESS); + } + + //remove 100 groups + for (int i = 104; i >= 5; i--) { + retval = rtr_mgr_remove_group(conf, i); + assert(retval == RTR_SUCCESS); + } + + rtr_mgr_remove_group(conf, 4); + + //try to remove last remainig group. + retval = rtr_mgr_remove_group(conf, 3); + assert(retval == RTR_ERROR); + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + free(groups[0].sockets); + free(group2.sockets); + free(group3.sockets); + free(group4.sockets); + free(group5.sockets); +} diff --git a/tests/test_getbits.c b/tests/test_getbits.c new file mode 100644 index 0000000..125ce94 --- /dev/null +++ b/tests/test_getbits.c @@ -0,0 +1,168 @@ +#include "rtrlib/lib/ip_private.h" + +#include <arpa/inet.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * @brief Test IPv4 address bit operations required by trie + */ +static void get_bits_testv4(void) +{ + struct lrtr_ip_addr addr; + struct lrtr_ip_addr result; + + addr.ver = LRTR_IPV4; + addr.u.addr4.addr = 0xAABBCC22; + + result = lrtr_ip_addr_get_bits(&addr, 0, 32); + assert(result.u.addr4.addr == 0xAABBCC22); + + result = lrtr_ip_addr_get_bits(&addr, 0, 1); + assert(result.u.addr4.addr == 0x80000000); + + result = lrtr_ip_addr_get_bits(&addr, 1, 1); + assert(result.u.addr4.addr == 0); + + result = lrtr_ip_addr_get_bits(&addr, 2, 1); + assert(result.u.addr4.addr == 0x20000000); + + result = lrtr_ip_addr_get_bits(&addr, 0, 8); + assert(result.u.addr4.addr == 0xAA000000); + + result = lrtr_ip_addr_get_bits(&addr, 8, 8); + assert(result.u.addr4.addr == 0x00BB0000); + + lrtr_ip_str_to_addr("10.10.10.0", &addr); + + result = lrtr_ip_addr_get_bits(&addr, 0, 8); + assert(lrtr_ip_str_cmp(&result, "10.0.0.0")); + + result = lrtr_ip_addr_get_bits(&addr, 0, 16); + assert(lrtr_ip_str_cmp(&result, "10.10.0.0")); + + result = lrtr_ip_addr_get_bits(&addr, 8, 8); + assert(lrtr_ip_str_cmp(&result, "0.10.0.0")); + + result = lrtr_ip_addr_get_bits(&addr, 8, 24); + assert(lrtr_ip_str_cmp(&result, "0.10.10.0")); + + result = lrtr_ip_addr_get_bits(&addr, 31, 1); + assert(result.u.addr4.addr == 0); + + result = lrtr_ip_addr_get_bits(&addr, 0, 1); + assert(result.u.addr4.addr == 0); + + result = lrtr_ip_addr_get_bits(&addr, 3, 3); + assert(lrtr_ip_str_cmp(&result, "8.0.0.0")); + + assert(lrtr_ip_str_to_addr("132.200.0.0", &addr) == 0); + result = lrtr_ip_addr_get_bits(&addr, 0, 1); + assert(result.u.addr4.addr == 0x80000000); + + assert(lrtr_ip_str_to_addr("101.200.0.0", &addr) == 0); + result = lrtr_ip_addr_get_bits(&addr, 0, 1); + assert(result.u.addr4.addr == 0); + + addr.u.addr4.addr = 0x6D698000; + result = lrtr_ip_addr_get_bits(&addr, 0, 19); + assert(result.u.addr4.addr == 0x6D698000); + + /* ip_str_to_addr("109.105.128.0", &addr); + * result = ip_addr_get_bits(&addr, 0, 8); + * printf("%u\n", result.u.addr4.addr); + */ + char buf[INET_ADDRSTRLEN]; + + assert(lrtr_ip_str_to_addr("10.10.10.5", &addr) == 0); + assert(lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)) == 0); + assert(strcmp("10.10.10.5", buf) == 0); +} + +/* + * @brief Test IPv6 address bit operations required by trie + */ +static void get_bits_testv6(void) +{ + struct lrtr_ip_addr addr; + struct lrtr_ip_addr result; + + addr.ver = LRTR_IPV6; + addr.u.addr6.addr[0] = 0x22AABBCC; + addr.u.addr6.addr[1] = 0xDDEEFF99; + addr.u.addr6.addr[2] = 0x33001122; + addr.u.addr6.addr[3] = 0x33445566; + + result = lrtr_ip_addr_get_bits(&addr, 0, 128); + assert(result.u.addr6.addr[0] == addr.u.addr6.addr[0] && result.u.addr6.addr[1] == addr.u.addr6.addr[1] && + result.u.addr6.addr[2] == addr.u.addr6.addr[2] && result.u.addr6.addr[3] == addr.u.addr6.addr[3]); + + result = lrtr_ip_addr_get_bits(&addr, 0, 64); + assert(result.u.addr6.addr[0] == addr.u.addr6.addr[0] && result.u.addr6.addr[1] == addr.u.addr6.addr[1] && + result.u.addr6.addr[2] == 0 && result.u.addr6.addr[3] == 0); + + bzero(&result, sizeof(result)); + result = lrtr_ip_addr_get_bits(&addr, 64, 64); + assert(result.u.addr6.addr[0] == 0); + assert(result.u.addr6.addr[1] == 0); + assert(result.u.addr6.addr[2] == addr.u.addr6.addr[2]); + assert(result.u.addr6.addr[3] == addr.u.addr6.addr[3]); + + result = lrtr_ip_addr_get_bits(&addr, 0, 8); + assert(result.u.addr6.addr[0] == 0x22000000 && result.u.addr6.addr[1] == 0); + + result = lrtr_ip_addr_get_bits(&addr, 64, 8); + assert(result.u.addr6.addr[1] == 0 && result.u.addr6.addr[2] == 0x33000000); + + result = lrtr_ip_addr_get_bits(&addr, 7, 8); + assert(result.u.addr6.addr[0] == 0xAA0000 && result.u.addr6.addr[1] == 0); + + result = lrtr_ip_addr_get_bits(&addr, 68, 7); + assert(result.u.addr6.addr[0] == 0 && result.u.addr6.addr[2] == 0x03000000); + + char buf[INET6_ADDRSTRLEN]; + + lrtr_ip_str_to_addr("fe80::862b:2bff:fe9a:f50f", &addr); + addr.ver = LRTR_IPV6; + assert(addr.u.addr6.addr[0] == 0xfe800000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0x862b2bff); + assert(addr.u.addr6.addr[3] == 0xfe9af50f); + + assert(lrtr_ip_str_to_addr("2001::", &addr) == 0); + assert(addr.u.addr6.addr[0] == 0x20010000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0); + + assert(lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)) == 0); + assert(strcmp("2001::", buf) == 0); + + lrtr_ip_str_to_addr("2001:0db8:85a3:08d3:1319:8a2e:0370:7344", &addr); + assert(lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)) == 0); + assert(strcmp("2001:db8:85a3:8d3:1319:8a2e:370:7344", buf) == 0); + + result = lrtr_ip_addr_get_bits(&addr, 0, 16); + assert(lrtr_ip_addr_to_str(&result, buf, sizeof(buf)) == 0); + assert(lrtr_ip_str_cmp(&result, "2001::")); + + result = lrtr_ip_addr_get_bits(&addr, 16, 16); + assert(lrtr_ip_addr_to_str(&result, buf, sizeof(buf)) == 0); + assert(lrtr_ip_str_cmp(&result, "0:db8::")); + result = lrtr_ip_addr_get_bits(&addr, 0, 1); + assert(lrtr_ip_str_cmp(&result, "::")); + + result = lrtr_ip_addr_get_bits(&addr, 126, 1); + assert(lrtr_ip_addr_to_str(&result, buf, sizeof(buf)) == 0); + assert(lrtr_ip_str_cmp(&result, "::")); +} + +int main(void) +{ + get_bits_testv4(); + get_bits_testv6(); + printf("Test successful\n"); + return EXIT_SUCCESS; +} diff --git a/tests/test_ht_spkitable.c b/tests/test_ht_spkitable.c new file mode 100644 index 0000000..ad6178d --- /dev/null +++ b/tests/test_ht_spkitable.c @@ -0,0 +1,649 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/rtr/rtr_private.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +/* test_ht_4 */ +#define NUM_TABLE_X (50) +#define NUM_TABLE_Y (50) +/* test_ht_6 */ +#define NUM_SKIS (10) +#define NUM_SKIS_RECORDS (10) +/* test_ht_7 */ +#define NUM_OF_SKI (200) +#define NUM_OF_RECORDS (2000) + +/** + * @brief Compare SPKI records for equality + * + * @return true if r1 == r2, false otherwise + */ +static bool spki_records_are_equal(struct spki_record *r1, struct spki_record *r2) +{ + if (r1->asn != r2->asn) + return false; + if (r1->socket != r2->socket) + return false; + if (memcmp(r1->ski, r2->ski, SKI_SIZE) != 0) + return false; + if (memcmp(r1->spki, r2->spki, SPKI_SIZE) != 0) + return false; + + return true; +} + +/** + * @brief Create a SPKI record + * + * @return new SPKI record + */ +static struct spki_record *create_record(int ASN, int ski_offset, int spki_offset, struct rtr_socket *socket) +{ + struct spki_record *record = malloc(sizeof(struct spki_record)); + uint32_t i; + + memset(record, 0, sizeof(*record)); + record->asn = ASN; + + for (i = 0; i < sizeof(record->ski) / sizeof(uint32_t); i++) + ((uint32_t *)record->ski)[i] = i + ski_offset; + + for (i = 0; i < sizeof(record->spki) / sizeof(uint32_t); i++) + ((uint32_t *)record->spki)[i] = i + spki_offset; + + record->socket = socket; + return record; +} + +/** + * @brief Helper shortcut, to assert insert of table entry + * Assert fails if memory allocation for new entry fails OR + * if the entry already exists. + */ +static void _spki_table_add_assert(struct spki_table *table, struct spki_record *record) +{ + assert(spki_table_add_entry(table, record) == SPKI_SUCCESS); +} + +/** + * @brief Helper shortcut, to assert remove of table entry + * Assert fails if entry does not exist in table, or any search error occurs. + */ +static void _spki_table_remove_assert(struct spki_table *table, struct spki_record *record) +{ + assert(spki_table_remove_entry(table, record) == SPKI_SUCCESS); +} + +/** + * @brief Helper shortcut, to assert search of table entry + * Assert fails if a matching enrty is found, but memory allocation fails OR + * if number of results does not match reference value: NUM_SKIS_RECORDS. + */ +static void _spki_table_search_assert(struct spki_table *table, uint8_t *ski) +{ + struct spki_record *result; + unsigned int result_len; + + assert(spki_table_search_by_ski(table, ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == NUM_SKIS_RECORDS); + free(result); +} + +/* ----- TESTS FUNCTIONS ----- */ + +/** + * @brief Test of spki_table_src_remove function + * Test if the spki_table_src_remove function is working correctly by adding + * spki_record associated with different rtr_socket. + * Then call spki_table_src_remove with one of the sockets as argument and + * validate that all records associated with different sockets are still there. + */ +static void test_ht_1(void) +{ + struct spki_table table; + struct rtr_socket *socket_one = malloc(sizeof(struct rtr_socket)); + struct rtr_socket *socket_two = malloc(sizeof(struct rtr_socket)); + uint8_t ski[SKI_SIZE]; + uint32_t asn = 1; + + /* test create record */ + struct spki_record *record = create_record(1, 0, 0, NULL); + + spki_table_init(&table, NULL); + memcpy(ski, record->ski, 20); + free(record); + + /* create and add records with either socket_one or socket_two */ + for (int i = 0; i < 255; i++) { + if (i % 2) + record = create_record(1, 0, i, socket_one); + else + record = create_record(1, 0, i, socket_two); + + _spki_table_add_assert(&table, record); + free(record); + } + + struct spki_record *result; + unsigned int result_len; + int count = 0; + /* verify all (255) records for asn and ski size */ + spki_table_get_all(&table, asn, ski, &result, &result_len); + for (unsigned int i = 0; i < result_len; i++) { + assert(result->asn == asn); + assert(memcmp(&result[i].ski, ski, SKI_SIZE) == 0); + count++; + } + assert(count == 255); + free(result); + + /* remove all records with socket_one */ + spki_table_src_remove(&table, socket_one); + /* verify remaining records have socket_two */ + spki_table_get_all(&table, asn, ski, &result, &result_len); + for (unsigned int i = 0; i < result_len; i++) { + assert(result[i].asn == asn); + assert(memcmp(&result[i].ski, ski, SKI_SIZE) == 0); + assert(result[i].socket == socket_two); + } + + /* cleanup: free memory */ + spki_table_free(&table); + free(result); + free(socket_one); + free(socket_two); + printf("%s() complete\n", __func__); +} + +/** + * @brief Test of spki_table_get_all with equal records + * Check the behaviour of spki_table_get_all if we add spki_record with + * different SPKI values but same SKI values (Hash collision). + */ +static void test_ht_2(void) +{ + struct spki_table table; + struct spki_record *result; + unsigned int result_len; + /* create 2 distinct records */ + struct spki_record *record1 = create_record(10, 20, 30, NULL); + struct spki_record *record2 = create_record(10, 20, 40, NULL); + /* TEST1: verify 2 diff SPKIs hash to the same SKI */ + spki_table_init(&table, NULL); + _spki_table_add_assert(&table, record1); + _spki_table_add_assert(&table, record2); + spki_table_get_all(&table, 10, record1->ski, &result, &result_len); + assert(result_len == 2); + + /* TEST2: check that returned records are the same we added. */ + assert(spki_records_are_equal(&result[0], record1) != spki_records_are_equal(&result[0], record2)); + assert(spki_records_are_equal(&result[1], record1) != spki_records_are_equal(&result[1], record2)); + free(result); + + /* TEST3: remove record1 and verify result is record2 */ + _spki_table_remove_assert(&table, record1); + spki_table_get_all(&table, 10, record1->ski, &result, &result_len); + assert(result_len == 1); + assert(spki_records_are_equal(&result[0], record2)); + free(result); + + /*TEST4: remove record2 and verify search result is empty */ + _spki_table_remove_assert(&table, record2); + spki_table_get_all(&table, 10, record1->ski, &result, &result_len); + assert(result_len == 0); + assert(!result); + spki_table_get_all(&table, 10, record2->ski, &result, &result_len); + assert(result_len == 0); + assert(!result); + + /* cleanup: free memory */ + free(record1); + free(record2); + spki_table_free(&table); + printf("%s() complete\n", __func__); +} + +/** + * @brief Test spki_table_get_all with records, differing in one attribute + * Test if the compare function for spki_record work correctly: + * Add spki_records which only differ in one attribute and delete one + * of the records, then check if the other records are still there. + */ +static void test_ht_3(void) +{ + struct spki_table table; + struct spki_record *result; + unsigned int result_len; + + /* create 4 distinct SPKI records, (at least) one parameter different */ + struct spki_record *record1 = create_record(10, 10, 10, NULL); + struct spki_record *record2 = create_record(10, 10, 11, NULL); + struct spki_record *record3 = create_record(10, 11, 10, NULL); + struct spki_record *record4 = create_record(11, 10, 10, NULL); + + /* TEST0:init table and add records -> checks compare function */ + spki_table_init(&table, NULL); + _spki_table_add_assert(&table, record1); + _spki_table_add_assert(&table, record2); + _spki_table_add_assert(&table, record3); + _spki_table_add_assert(&table, record4); + + /* TEST1: remove record1, check others -------------------------------*/ + _spki_table_remove_assert(&table, record1); + /* Check if other records are still there */ + assert(spki_table_get_all(&table, record2->asn, record2->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + + assert(spki_table_get_all(&table, record3->asn, record3->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + + assert(spki_table_get_all(&table, record4->asn, record4->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + /* (re)add record1 */ + _spki_table_add_assert(&table, record1); + + /* TEST2: remove record2, check others -------------------------------*/ + _spki_table_remove_assert(&table, record2); + /* Check if other records are still there */ + assert(spki_table_get_all(&table, record1->asn, record1->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + + assert(spki_table_get_all(&table, record3->asn, record3->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + + assert(spki_table_get_all(&table, record4->asn, record4->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + /* (re)add record2 */ + _spki_table_add_assert(&table, record2); + + /* TEST3: remove record3, check others -------------------------------*/ + _spki_table_remove_assert(&table, record3); + /* Check if other records are still there */ + assert(spki_table_get_all(&table, record1->asn, record1->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 2); + free(result); + + assert(spki_table_get_all(&table, record2->asn, record2->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 2); + free(result); + + assert(spki_table_get_all(&table, record4->asn, record4->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + /* (re)add record3 */ + _spki_table_add_assert(&table, record3); + + /* TEST4: remove record4, check others -------------------------------*/ + _spki_table_remove_assert(&table, record4); + /* Check if other records are still there */ + assert(spki_table_get_all(&table, record1->asn, record1->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 2); + free(result); + + assert(spki_table_get_all(&table, record2->asn, record2->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 2); + free(result); + + assert(spki_table_get_all(&table, record3->asn, record3->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + + /* cleanup: free memory */ + spki_table_free(&table); + free(record1); + free(record2); + free(record3); + free(record4); + printf("%s complete\n", __func__); +} + +/** + * @brief Test remove records one by one until spki table is empty + * Test if all added records can be deleted and test if any of the added + * records retain in the table although they got deleted. + */ +static void test_ht_4(void) +{ + struct spki_table table; + struct spki_record *result; + unsigned int result_len; + struct spki_record *records[NUM_TABLE_X][NUM_TABLE_Y]; + + spki_table_init(&table, NULL); + /* Add 50 * 50 entries */ + for (int i = 0; i < NUM_TABLE_X; i++) { + for (int j = 0; j < NUM_TABLE_Y; j++) { + records[i][j] = create_record(i, j, j, NULL); + _spki_table_add_assert(&table, records[i][j]); + } + } + + /* Check if every record is there and then delete it */ + for (int i = 0; i < NUM_TABLE_X; i++) { + for (int j = 0; j < NUM_TABLE_Y; j++) { + assert(spki_table_get_all(&table, records[i][j]->asn, records[i][j]->ski, &result, + &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + _spki_table_remove_assert(&table, records[i][j]); + free(result); + free(records[i][j]); + } + } + + /* Add all record again and look for SPKI_DUPLICATE_RECORD */ + for (int i = 0; i < NUM_TABLE_X; i++) { + for (int j = 0; j < NUM_TABLE_Y; j++) { + records[i][j] = create_record(i, j, j, NULL); + _spki_table_add_assert(&table, records[i][j]); + free(records[i][j]); + } + } + + /* cleanup: free memory */ + spki_table_free(&table); + printf("%s() complete\n", __func__); +} + +/** + * @brief Test for SPKI_DUPLICATE_RECORD + * Test the behavior if equal spki_records get added to the table. + */ +static void test_ht_5(void) +{ + struct spki_table table; + /* create 2 equal records */ + struct spki_record *record1 = create_record(10, 10, 10, NULL); + struct spki_record *record2 = create_record(10, 10, 10, NULL); + + /* create table and (try) add records -> check for duplicates */ + spki_table_init(&table, NULL); + assert(spki_table_add_entry(&table, record1) == SPKI_SUCCESS); + assert(spki_table_add_entry(&table, record2) == SPKI_DUPLICATE_RECORD); + assert(spki_table_add_entry(&table, record1) == SPKI_DUPLICATE_RECORD); + + struct spki_record *result; + unsigned int result_len; + + /* check that only record1 is in table and matches query */ + spki_table_get_all(&table, 10, record1->ski, &result, &result_len); + assert(result_len == 1); + free(result); + + /* cleanup: free memory */ + spki_table_free(&table); + free(record1); + free(record2); + printf("%s() complete\n", __func__); +} + +/** + * @brief Test spki_table_search_by_ski + * Test if all spki_records with the same SKI get returned. + */ +static void test_ht_6(void) +{ + struct spki_table table; + struct spki_record *records[NUM_SKIS][NUM_SKIS_RECORDS]; + /* Add the records to the table */ + spki_table_init(&table, NULL); + for (unsigned int i = 0; i < NUM_SKIS; i++) { + for (unsigned int j = 0; j < NUM_SKIS_RECORDS; j++) { + records[i][j] = create_record(j, i, j, NULL); + _spki_table_add_assert(&table, records[i][j]); + } + } + + /* Search for records by SKI and check result */ + for (unsigned int i = 0; i < NUM_SKIS; i++) { + for (unsigned int j = 0; j < NUM_SKIS_RECORDS; j++) { + _spki_table_search_assert(&table, records[i][j]->ski); + free(records[i][j]); + } + } + + /* cleanup: free memory */ + spki_table_free(&table); + printf("%s() complete\n", __func__); +} + +/** + * @brief Test releation between ASN and SKI + * This tests has 3 parts, in each part spki records are created and validated: + * a) spki records with same ASN but different SKI: ASN(1) <---> SKI(n) + * b) spki records with different ASN but same SKI: ASN(n) <---> SKI(1) + * c) spki records with different ASN and diff SKI: ASN(n) <---> SKI(n) + */ +static void test_ht_7(void) +{ + struct spki_table table; + struct spki_record *records[NUM_OF_RECORDS]; + /* ASN(1) <---> SKI(n) ---------------------------------------------- */ + spki_table_init(&table, NULL); + /* Create NUM_OF_RECORDS spki_records with same ASN but different SKI */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + records[i] = create_record(2555, i, i, NULL); + _spki_table_add_assert(&table, records[i]); + } + + /* Validate */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + struct spki_record *result; + unsigned int size = 0; + + spki_table_get_all(&table, 2555, records[i]->ski, &result, &size); + + assert(size == 1); + assert(spki_records_are_equal(records[i], &result[0])); + free(result); + free(records[i]); + } + spki_table_free(&table); + + /* ASN(n) <---> SKI(1) ---------------------------------------------- */ + spki_table_init(&table, NULL); + /* Create NUM_OF_RECORDS spki_records with same SKI but different ASN */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + records[i] = create_record(i, 100, 100, NULL); + _spki_table_add_assert(&table, records[i]); + } + + /* Validate */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + struct spki_record *result; + unsigned int size = 0; + + spki_table_get_all(&table, i, records[NUM_OF_RECORDS - 1]->ski, &result, &size); + + assert(size == 1); + assert(spki_records_are_equal(records[i], &result[0])); + free(result); + free(records[i]); + } + spki_table_free(&table); + + struct spki_record *records_n_n[NUM_OF_RECORDS][NUM_OF_SKI]; + /* ASN(n) <---> SKI(n) ---------------------------------------------- */ + spki_table_init(&table, NULL); + /* Create: {NUM_OF_RECORDS} x {NUM_OF_SKI} spki_records */ + /* {ASN_0,ASN_1...} x {SKI_0, SKI_1...} */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + for (unsigned int j = 0; j < NUM_OF_SKI; j++) { + records_n_n[i][j] = create_record(i, j, j, NULL); + _spki_table_add_assert(&table, records_n_n[i][j]); + } + } + + /* Validate */ + for (unsigned int i = 0; i < NUM_OF_RECORDS; i++) { + for (unsigned int j = 0; j < NUM_OF_SKI; j++) { + struct spki_record *result; + unsigned int size = 0; + + spki_table_get_all(&table, i, records_n_n[i][j]->ski, &result, &size); + + assert(size == 1); + assert(spki_records_are_equal(records_n_n[i][j], &result[0])); + free(result); + free(records_n_n[i][j]); + } + } + + /* cleanup: free memory */ + spki_table_free(&table); + printf("%s() complete\n", __func__); +} + +static void test_table_swap(void) +{ + struct spki_table table1; + struct spki_table table2; + + struct spki_record *test_record1 = create_record(1, 10, 100, NULL); + struct spki_record *test_record2 = create_record(2, 20, 200, NULL); + + spki_table_init(&table1, NULL); + spki_table_init(&table2, NULL); + + _spki_table_add_assert(&table1, test_record1); + _spki_table_add_assert(&table2, test_record2); + + struct spki_record *result; + unsigned int result_len; + + assert(spki_table_search_by_ski(&table1, test_record1->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + result = NULL; + + assert(spki_table_search_by_ski(&table2, test_record2->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + result = NULL; + + spki_table_swap(&table1, &table2); + + assert(spki_table_search_by_ski(&table1, test_record2->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + result = NULL; + + assert(spki_table_search_by_ski(&table2, test_record1->ski, &result, &result_len) == SPKI_SUCCESS); + assert(result_len == 1); + free(result); + result = NULL; + + spki_table_free(&table1); + spki_table_free(&table2); + free(test_record1); + free(test_record2); + + printf("%s() complete\n", __func__); +} + +static void update_spki(struct spki_table *s __attribute__((unused)), const struct spki_record record, const bool added) +{ + printf("%c ASN: %u\n", (added ? '+' : '-'), record.asn); + + int i; + int size = sizeof(record.ski); + + printf("SKI: "); + for (i = 0; i < size; i++) { + printf("%02x", record.ski[i]); + if (i < size - 1) + printf(":"); + } + printf("\n "); + + i = 0; + size = sizeof(record.spki); + printf("SPKI: "); + for (i = 0; i < size; i++) { + if ((i % 40 == 0) && (i != 0)) + printf("\n "); + + printf("%02x", record.spki[i]); + if (i < size - 1) + printf(":"); + } + printf("\n"); + + if (record.asn == 1) + assert(!added); + if (record.asn == 2) + assert(false); + if (record.asn == 3) + assert(added); +} + +static void test_table_diff(void) +{ + struct spki_table table1; + struct spki_table table2; + struct rtr_socket *socket = (struct rtr_socket *)1; + + struct spki_record *test_record1 = create_record(1, 10, 100, socket); + struct spki_record *test_record2 = create_record(2, 20, 200, socket); + struct spki_record *test_record3 = create_record(3, 30, 300, socket); + + spki_table_init(&table1, NULL); + spki_table_init(&table2, NULL); + + printf("Adding to table 1\n"); + _spki_table_add_assert(&table1, test_record1); + _spki_table_add_assert(&table1, test_record2); + + printf("Adding to table 2\n"); + _spki_table_add_assert(&table2, test_record2); + _spki_table_add_assert(&table2, test_record3); + + printf("Calculating diff\n"); + table1.update_fp = update_spki; + table2.update_fp = update_spki; + spki_table_notify_diff(&table2, &table1, socket); + table1.update_fp = NULL; + table2.update_fp = NULL; + + printf("Freeing tables\n"); + spki_table_free(&table1); + spki_table_free(&table2); + free(test_record1); + free(test_record2); + free(test_record3); + + printf("%s() complete\n", __func__); +} + +int main(void) +{ + test_ht_1(); + test_ht_2(); + test_ht_3(); + test_ht_4(); + test_ht_5(); + test_ht_6(); + test_ht_7(); + test_table_swap(); + test_table_diff(); + return EXIT_SUCCESS; +} diff --git a/tests/test_ht_spkitable_locks.c b/tests/test_ht_spkitable_locks.c new file mode 100644 index 0000000..9e19810 --- /dev/null +++ b/tests/test_ht_spkitable_locks.c @@ -0,0 +1,174 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +struct add_records_args { + struct spki_table *table; + int start_asn; + int count; +}; + +struct remove_records_args { + struct spki_table *table; + int start_asn; + int count; +}; + +static struct spki_record *create_record(int ASN, int ski_offset, int spki_offset, struct rtr_socket *socket); + +/** + * @brief Compare SPKI records for equality + * + * @return true if r1 == r2, false otherwise + */ +static bool compare_spki_records(struct spki_record *r1, struct spki_record *r2) +{ + if (r1->asn != r2->asn) + return false; + if (r1->socket != r2->socket) + return false; + if (memcmp(r1->ski, r2->ski, SKI_SIZE) != 0) + return false; + if (memcmp(r1->spki, r2->spki, SPKI_SIZE) != 0) + return false; + + return true; +} + +/** + * @brief Create a SPKI record + * + * @return new SPKI record + */ +static struct spki_record *create_record(int ASN, int ski_offset, int spki_offset, struct rtr_socket *socket) +{ + struct spki_record *record = malloc(sizeof(struct spki_record)); + uint32_t i; + + record->asn = ASN; + + for (i = 0; i < sizeof(record->ski); i++) + record->ski[i] = i + ski_offset; + + for (i = 0; i < sizeof(record->spki); i++) + record->spki[i] = i + spki_offset; + + record->socket = socket; + return record; +} + +/** + * @brief Add records to spki table + * Add 'args->count' records to the spki table 'args->table', start with + * ASN 'args->start_asn'. + */ +static void *add_records(struct add_records_args *args) +{ + printf("Add %i records: ASN [%i..%i]\n", args->count, args->start_asn, args->count + args->start_asn - 1); + for (int i = args->start_asn; i < args->count + args->start_asn; i++) { + struct spki_record *record = create_record(i, i, i, NULL); + int ret = spki_table_add_entry(args->table, record); + + assert(ret == SPKI_SUCCESS); + free(record); + } + + return NULL; +} + +/** + * @brief remove records from spki table + * Remove 'args->count' records from the spki table 'args->table'. + */ +static void *remove_records(struct remove_records_args *args) +{ + printf("Remove %i records: ASN [%i..%i]\n", args->count, args->start_asn, args->count + args->start_asn - 1); + for (int i = args->start_asn; i < args->count + args->start_asn; i++) { + struct spki_record *record = create_record(i, i, i, NULL); + int ret = spki_table_remove_entry(args->table, record); + + assert(ret == SPKI_SUCCESS); + free(record); + } + + return NULL; +} + +/** + * @brief Test concurrent add and delete operations on spki table + */ +static void lock_test1(void) +{ + unsigned int max_threads = 20; + unsigned int records_per_thread = 10000; + struct spki_table spkit; + struct add_records_args args[max_threads]; + + spki_table_init(&spkit, NULL); + pthread_t threads[max_threads]; + + /* Concurrently add SPKI records to the table */ + for (unsigned int i = 0; i < max_threads; i++) { + args[i].table = &spkit; + args[i].start_asn = i * records_per_thread; + args[i].count = records_per_thread; + pthread_create(&threads[i], NULL, (void *(*)(void *))add_records, &args[i]); + } + + /* Wait for parallel add operations to finish */ + for (unsigned int i = 0; i < max_threads; i++) { + pthread_join(threads[i], NULL); + printf("Thread %i returned\n", i); + } + + /* Check all records for successful add */ + struct spki_record *result; + unsigned int result_size = 0; + + for (unsigned int i = 0; i < records_per_thread * max_threads; i++) { + struct spki_record *record = create_record(i, i, i, NULL); + + spki_table_get_all(&spkit, record->asn, record->ski, &result, &result_size); + assert(result_size == 1); + assert(compare_spki_records(record, &result[0])); + free(result); + free(record); + } + + struct remove_records_args remove_args[max_threads]; + /* Concurrently delete SPKI records */ + for (unsigned int i = 0; i < max_threads; i++) { + remove_args[i].table = &spkit; + remove_args[i].start_asn = i * records_per_thread; + remove_args[i].count = records_per_thread; + pthread_create(&threads[i], NULL, (void *(*)(void *))remove_records, &remove_args[i]); + } + + /* Wait for parallel delete operation to finish */ + for (unsigned int i = 0; i < max_threads; i++) { + pthread_join(threads[i], NULL); + printf("Thread %i returned\n", i); + } + + /* cleanup: free spki_table */ + spki_table_free(&spkit); + printf("%s() complete\n", __func__); +} + +int main(void) +{ + lock_test1(); + return EXIT_SUCCESS; +} diff --git a/tests/test_ipaddr.c b/tests/test_ipaddr.c new file mode 100644 index 0000000..c0fea5b --- /dev/null +++ b/tests/test_ipaddr.c @@ -0,0 +1,256 @@ + +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/lib/ip.h" + +#include <arpa/inet.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * @brief test ipv4 parsing + */ +static void test_v4(void) +{ + struct lrtr_ip_addr addr; + char buf[INET_ADDRSTRLEN]; + + lrtr_ip_str_to_addr("0.0.0.0", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("0.0.0.0", buf) == 0); + + lrtr_ip_str_to_addr("255.255.255.255", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0xffffffff); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("255.255.255.255", buf) == 0); + + lrtr_ip_str_to_addr("0.2.6.7", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0x20607); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("0.2.6.7", buf) == 0); + + lrtr_ip_str_to_addr("78.69.255.0", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0x4e45ff00); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("78.69.255.0", buf) == 0); + + lrtr_ip_str_to_addr("1.1.1.1", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0x1010101); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("1.1.1.1", buf) == 0); + + lrtr_ip_str_to_addr("5.0.255.255", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0x500ffff); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("5.0.255.255", buf) == 0); + + lrtr_ip_str_to_addr("8.9.6.3", &addr); + assert(addr.ver == LRTR_IPV4); + assert(addr.u.addr4.addr == 0x8090603); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("8.9.6.3", buf) == 0); + + /* check some malformed addresses */ + assert(lrtr_ip_str_to_addr("8,3,4,5", &addr) == -1); + assert(lrtr_ip_str_to_addr("8.4.5", &addr) == -1); +} + +/* + * @brief test ipv6 parsing + */ +static void test_v6(void) +{ + struct lrtr_ip_addr addr; + char buf[INET6_ADDRSTRLEN]; + + lrtr_ip_str_to_addr("fdf8:f53b:82e4::53", &addr); + assert(addr.u.addr6.addr[0] == 0xfdf8f53b); + assert(addr.u.addr6.addr[1] == 0x82e40000); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x53); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("fdf8:f53b:82e4::53", buf) == 0); + + lrtr_ip_str_to_addr("fe80::200:5aee:feaa:20a2", &addr); + assert(addr.u.addr6.addr[0] == 0xfe800000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0x2005aee); + assert(addr.u.addr6.addr[3] == 0xfeaa20a2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("fe80::200:5aee:feaa:20a2", buf) == 0); + + lrtr_ip_str_to_addr("2001::1", &addr); + assert(addr.u.addr6.addr[0] == 0x20010000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x1); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001::1", buf) == 0); + + lrtr_ip_str_to_addr("2001:0:4136:e378:8000:63bf:3fff:fdd2", &addr); + assert(addr.u.addr6.addr[0] == 0x20010000); + assert(addr.u.addr6.addr[1] == 0x4136e378); + assert(addr.u.addr6.addr[2] == 0x800063bf); + assert(addr.u.addr6.addr[3] == 0x3ffffdd2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001:0:4136:e378:8000:63bf:3fff:fdd2", buf) == 0); + + lrtr_ip_str_to_addr("2001:2:6c::430", &addr); + assert(addr.u.addr6.addr[0] == 0x20010002); + assert(addr.u.addr6.addr[1] == 0x6C0000); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x430); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001:2:6c::430", buf) == 0); + + lrtr_ip_str_to_addr("2001:10:240:ab::a", &addr); + assert(addr.u.addr6.addr[0] == 0x20010010); + assert(addr.u.addr6.addr[1] == 0x24000AB); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0xa); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001:10:240:ab::a", buf) == 0); + + lrtr_ip_str_to_addr("2002:cb0a:3cdd:1::1", &addr); + assert(addr.u.addr6.addr[0] == 0x2002cb0a); + assert(addr.u.addr6.addr[1] == 0x3cdd0001); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x1); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2002:cb0a:3cdd:1::1", buf) == 0); + + lrtr_ip_str_to_addr("2001:db8:8:4::2", &addr); + assert(addr.u.addr6.addr[0] == 0x20010db8); + assert(addr.u.addr6.addr[1] == 0x80004); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001:db8:8:4::2", buf) == 0); + + lrtr_ip_str_to_addr("FF01:0:0:0:0:0:0:2", &addr); + assert(addr.u.addr6.addr[0] == 0xff010000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("ff01::2", buf) == 0); + + lrtr_ip_str_to_addr("fdf8:f53b:82e4::53", &addr); + assert(addr.u.addr6.addr[0] == 0xfdf8f53b); + assert(addr.u.addr6.addr[1] == 0x82e40000); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0x53); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("fdf8:f53b:82e4::53", buf) == 0); + + lrtr_ip_str_to_addr("fe80::200:5aee:feaa:20a2", &addr); + assert(addr.u.addr6.addr[0] == 0xfe800000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0x2005aee); + assert(addr.u.addr6.addr[3] == 0xfeaa20a2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("fe80::200:5aee:feaa:20a2", buf) == 0); + + lrtr_ip_str_to_addr("2001::1", &addr); + assert(addr.u.addr6.addr[0] == 0x20010000); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 1); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001::1", buf) == 0); + + lrtr_ip_str_to_addr("2001:0:4136:e378:8000:63bf:3fff:fdd2", &addr); + assert(addr.u.addr6.addr[0] == 0x20010000); + assert(addr.u.addr6.addr[1] == 0x4136e378); + assert(addr.u.addr6.addr[2] == 0x800063bf); + assert(addr.u.addr6.addr[3] == 0x3ffffdd2); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("2001:0:4136:e378:8000:63bf:3fff:fdd2", buf) == 0); + + /* test embedded ipv4 */ + lrtr_ip_str_to_addr("::ffff:192.0.2.128", &addr); + assert(addr.u.addr6.addr[0] == 0); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0xffff); + assert(addr.u.addr6.addr[3] == 0xc0000280); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("::ffff:192.0.2.128", buf) == 0); + + lrtr_ip_str_to_addr("::10.58.64.34", &addr); + assert(addr.u.addr6.addr[0] == 0); + assert(addr.u.addr6.addr[1] == 0); + assert(addr.u.addr6.addr[2] == 0); + assert(addr.u.addr6.addr[3] == 0xa3a4022); + lrtr_ip_addr_to_str(&addr, buf, sizeof(buf)); + assert(strcmp("::10.58.64.34", buf) == 0); + + /* test check for malformed embedded ipv4 */ + assert(lrtr_ip_str_to_addr("::ffff:192.0,2.128", &addr) == -1); + + /* buffer size check*/ + assert(lrtr_ip_addr_to_str(&addr, buf, 10) == -1); + + /* test leading single colon check */ + assert(lrtr_ip_str_to_addr(":ffff::ffff", &addr) == -1); + + /* test multiple double colons check */ + assert(lrtr_ip_str_to_addr("::ffff::ffff", &addr) == -1); + + /* test check for to long addresses */ + assert(lrtr_ip_str_to_addr("2001:0:6:8:0:f:3fff:fdd2:55", &addr) == -1); + + /* test check for to big groups */ + assert(lrtr_ip_str_to_addr("::fffff", &addr) == -1); + + /* check for null byte in address string */ + assert(lrtr_ip_str_to_addr("2001:\0::", &addr) == -1); +} + +/* + * @brief test ip comparisons + */ +static void test_cmp(void) +{ + struct lrtr_ip_addr addr1, addr2; + + lrtr_ip_str_to_addr("2001:0:4136:e378:8000:63bf:3fff:fdd2", &addr1); + lrtr_ip_str_to_addr("2001:0:4136:e378:8000:63bf:3fff:fdd2", &addr2); + + assert(lrtr_ip_addr_equal(addr1, addr2) == true); + + lrtr_ip_str_to_addr("2001:0:4136:e378:8000:63bf:3fff:fdd3", &addr2); + assert(lrtr_ip_addr_equal(addr1, addr2) == false); + + lrtr_ip_str_to_addr("141.22.5.22", &addr2); + assert(lrtr_ip_addr_equal(addr1, addr2) == false); + + lrtr_ip_str_to_addr("141.22.5.22", &addr1); + assert(lrtr_ip_addr_equal(addr1, addr2) == true); + + lrtr_ip_str_to_addr("141.26.5.23", &addr1); +} + +int main(void) +{ + test_v4(); + test_v6(); + test_cmp(); + printf("Test successful\n"); + return EXIT_SUCCESS; +} diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c new file mode 100644 index 0000000..73c8575 --- /dev/null +++ b/tests/test_live_validation.c @@ -0,0 +1,121 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/rtrlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +struct test_validity_query { + const char *pfx; + int len; + int asn; + unsigned int val; +}; + +/* + * Verification is based on ROAs for RIPE RIS Routing Beacons, see: + * (https://www.ripe.net/analyse/internet-measurements/ + * routing-information-service-ris/current-ris-routing-beacons) + */ +const struct test_validity_query queries[] = {{"93.175.146.0", 24, 12654, BGP_PFXV_STATE_VALID}, + {"2001:7fb:fd02::", 48, 12654, BGP_PFXV_STATE_VALID}, + {"93.175.147.0", 24, 12654, BGP_PFXV_STATE_INVALID}, + {"2001:7fb:fd03::", 48, 12654, BGP_PFXV_STATE_INVALID}, + {"84.205.83.0", 24, 12654, BGP_PFXV_STATE_NOT_FOUND}, + {"2001:7fb:ff03::", 48, 12654, BGP_PFXV_STATE_NOT_FOUND}, + {NULL, 0, 0, 0} }; + +const int connection_timeout = 20; +enum rtr_mgr_status connection_status = -1; + +static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), + enum rtr_mgr_status status, + const struct rtr_socket *socket __attribute__((unused)), + void *data __attribute__((unused))) +{ + if (status == RTR_MGR_ERROR) + connection_status = status; +} + +/** + * @brief live prefix validation test + * This test requires an active network connection. It runs an on-line live + * validation of specific IP prefixes, i.e., RIPE BGP beacons, that have known + * RPKI validation states. This tests uses a TCP transport connection. + */ +int main(void) +{ + /* These variables are not in the global scope + * because it would cause warnings about discarding constness + */ + char RPKI_CACHE_HOST[] = "rpki-validator.realmv6.org"; + char RPKI_CACHE_POST[] = "8283"; + + /* create a TCP transport socket */ + struct tr_socket tr_tcp; + struct tr_tcp_config tcp_config = {RPKI_CACHE_HOST, RPKI_CACHE_POST, NULL, NULL, NULL, 0}; + struct rtr_socket rtr_tcp; + struct rtr_mgr_group groups[1]; + + /* init a TCP transport and create rtr socket */ + tr_tcp_init(&tcp_config, &tr_tcp); + rtr_tcp.tr_socket = &tr_tcp; + + /* create a rtr_mgr_group array with 1 element */ + groups[0].sockets = malloc(1 * sizeof(struct rtr_socket *)); + groups[0].sockets_len = 1; + groups[0].sockets[0] = &rtr_tcp; + groups[0].preference = 1; + + struct rtr_mgr_config *conf; + + if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL) < 0) + return EXIT_FAILURE; + + rtr_mgr_start(conf); + int sleep_counter = 0; + /* wait for connection, or timeout and exit eventually */ + while (!rtr_mgr_conf_in_sync(conf)) { + if (connection_status == RTR_MGR_ERROR) + return EXIT_FAILURE; + + sleep(1); + sleep_counter++; + if (sleep_counter >= connection_timeout) + return EXIT_FAILURE; + } + + int i = 0; + struct test_validity_query q = queries[i]; + /* test validity of entries in queries[] */ + while (q.pfx) { + struct lrtr_ip_addr pref; + enum pfxv_state result; + struct pfx_record *reason = NULL; + unsigned int reason_len = 0; + + lrtr_ip_str_to_addr(q.pfx, &pref); + pfx_table_validate_r(groups[0].sockets[0]->pfx_table, &reason, &reason_len, q.asn, &pref, q.len, + &result); + if (result != q.val) { + printf("ERROR: prefix validation mismatch.\n"); + return EXIT_FAILURE; + } + printf("%s/%d \tOK\n", q.pfx, q.len); + q = queries[++i]; + } + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + + return EXIT_SUCCESS; +} diff --git a/tests/test_pfx.c b/tests/test_pfx.c new file mode 100644 index 0000000..e7f2651 --- /dev/null +++ b/tests/test_pfx.c @@ -0,0 +1,450 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/lib/ip_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/pfx/pfx.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/pfx/trie/trie_private.h" +#include "rtrlib/rtr/rtr.h" + +#include <arpa/inet.h> +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +static void validate(struct pfx_table *pfxt, uint32_t asn, const char *prefix, uint8_t prefix_len, + enum pfxv_state expected_result) +{ + struct lrtr_ip_addr ip; + enum pfxv_state val_res; + + assert(!lrtr_ip_str_to_addr(prefix, &ip)); + + assert(pfx_table_validate(pfxt, asn, &ip, prefix_len, &val_res) == PFX_SUCCESS); + + assert(val_res == expected_result); +} + +/** + * @brief remove_src_test + * This test verifies pfx_table_src_remove function. It first adds certain + * records with different sockets into a pfx_table. Afterwards entries with + * socket tr1 are removed, and the remaining records are verified. + */ +static void remove_src_test(void) +{ + struct pfx_table pfxt; + struct rtr_socket tr1; + struct pfx_record pfx; + /* TEST1: init prefix table ----------------------------------------- */ + pfx_table_init(&pfxt, NULL); + pfx.min_len = 32; + pfx.max_len = 32; + /* TEST2: add and verify different prefixes -------------------------- */ + pfx.asn = 80; + pfx.socket = &tr1; + lrtr_ip_str_to_addr("10.11.10.0", &pfx.prefix); + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + pfx.asn = 90; + pfx.socket = NULL; + lrtr_ip_str_to_addr("10.11.10.0", &pfx.prefix); + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + pfx.socket = NULL; + pfx.min_len = 24; + lrtr_ip_str_to_addr("192.168.0.0", &pfx.prefix); + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + pfx.socket = &tr1; + pfx.min_len = 8; + lrtr_ip_str_to_addr("10.0.0.0", &pfx.prefix); + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + unsigned int len = 0; + struct trie_node **array = NULL; + /* verify that table has 3 distinct prefix entries */ + assert(trie_get_children(pfxt.ipv4, &array, &len) != -1); + free(array); + array = NULL; + assert((len + 1) == 3); + + /* remove entries with socket tr1, verify remaining 2 records */ + pfx_table_src_remove(&pfxt, &tr1); + len = 0; + assert(trie_get_children(pfxt.ipv4, &array, &len) != -1); + free(array); + assert((len + 1) == 2); + + /* verify validation of prefixes */ + validate(&pfxt, 90, "10.0.0.0", 8, BGP_PFXV_STATE_NOT_FOUND); + + validate(&pfxt, 90, "10.11.10.0", 32, BGP_PFXV_STATE_VALID); + + validate(&pfxt, 80, "10.11.10.0", 32, BGP_PFXV_STATE_INVALID); + + /* cleanup: free table */ + pfx_table_free(&pfxt); + printf("%s() successful\n", __func__); +} + +/** + * @brief Test pfx_table operations with many records + * This test adds, validates, and removes a huge number of records in/to/from + * a pfx_table. + */ +static void mass_test(void) +{ + struct pfx_table pfxt; + struct pfx_record pfx; + enum pfxv_state res; + const uint32_t min_i = 0xFFFF0000; + const uint32_t max_i = 0xFFFFFFF0; + /* init table with huge number of records */ + pfx_table_init(&pfxt, NULL); + printf("Inserting %u records\n", (max_i - min_i) * 3); + for (uint32_t i = max_i; i >= min_i; i--) { + pfx.min_len = 32; + pfx.max_len = 32; + pfx.socket = NULL; + pfx.asn = i; + pfx.prefix.u.addr4.addr = htonl(i); + pfx.prefix.ver = LRTR_IPV4; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + /* add same prefix, with diff asn */ + pfx.asn = i + 1; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + /* add same prefix, with diff len */ + pfx.min_len = 128; + pfx.max_len = 128; + pfx.prefix.ver = LRTR_IPV6; + ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; + ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + } + + /* verify validation of huge number of records */ + printf("validating..\n"); + for (uint32_t i = max_i; i >= min_i; i--) { + pfx.min_len = 32; + pfx.max_len = 32; + pfx.prefix.ver = LRTR_IPV4; + pfx.prefix.u.addr4.addr = htonl(i); + assert(pfx_table_validate(&pfxt, i, &pfx.prefix, pfx.min_len, &res) == PFX_SUCCESS); + assert(res == BGP_PFXV_STATE_VALID); + assert(pfx_table_validate(&pfxt, i + 1, &pfx.prefix, pfx.min_len, &res) == PFX_SUCCESS); + assert(res == BGP_PFXV_STATE_VALID); + + pfx.min_len = 128; + pfx.max_len = 128; + pfx.prefix.ver = LRTR_IPV6; + ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; + ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + + assert(pfx_table_validate(&pfxt, i + 1, &pfx.prefix, pfx.min_len, &res) == PFX_SUCCESS); + assert(res == BGP_PFXV_STATE_VALID); + } + + /* verify removal of huge number of records */ + printf("removing records\n"); + for (uint32_t i = max_i; i >= min_i; i--) { + pfx.socket = NULL; + pfx.min_len = 32; + pfx.max_len = 32; + pfx.asn = i; + pfx.prefix.ver = LRTR_IPV4; + pfx.prefix.u.addr4.addr = htonl(i); + assert(pfx_table_remove(&pfxt, &pfx) == PFX_SUCCESS); + + pfx.asn = i + 1; + assert(pfx_table_remove(&pfxt, &pfx) == PFX_SUCCESS); + + pfx.prefix.ver = LRTR_IPV6; + pfx.min_len = 128; + pfx.max_len = 128; + ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; + ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + assert(pfx_table_remove(&pfxt, &pfx) == PFX_SUCCESS); + } + + /* cleanup: free table */ + pfx_table_free(&pfxt); + printf("%s() successful\n", __func__); +} + +/** + * @brief Test of pfx_table functions + * This test verifies pfx_table and its core functions, namely + * pfx_table_add and pfx_table_validate. + */ +static void pfx_table_test(void) +{ + struct pfx_table pfxt; + struct pfx_record pfx; + enum pfxv_state res; + + pfx_table_init(&pfxt, NULL); + + pfx.asn = 123; + pfx.prefix.ver = LRTR_IPV4; + lrtr_ip_str_to_addr("10.10.0.0", &pfx.prefix); + pfx.min_len = 16; + pfx.max_len = 24; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + validate(&pfxt, 123, "10.10.0.0", 16, BGP_PFXV_STATE_VALID); + validate(&pfxt, 124, "10.10.0.0", 16, BGP_PFXV_STATE_INVALID); + validate(&pfxt, 123, "10.10.0.0", 24, BGP_PFXV_STATE_VALID); + validate(&pfxt, 123, "10.10.10.0", 20, BGP_PFXV_STATE_VALID); + validate(&pfxt, 123, "10.10.10.0", 25, BGP_PFXV_STATE_INVALID); + validate(&pfxt, 123, "10.11.10.0", 16, BGP_PFXV_STATE_NOT_FOUND); + + lrtr_ip_str_to_addr("10.10.0.0", &pfx.prefix); + pfx.asn = 122; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 122, "10.10.0.0", 18, BGP_PFXV_STATE_VALID); + + lrtr_ip_str_to_addr("11.10.0.0", &pfx.prefix); + pfx.asn = 22; + pfx.min_len = 17; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 22, "11.10.0.0", 17, BGP_PFXV_STATE_VALID); + + lrtr_ip_str_to_addr("2a01:4f8:131::", &pfx.prefix); + pfx.prefix.ver = LRTR_IPV6; + pfx.min_len = 48; + pfx.max_len = 48; + pfx.asn = 124; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + assert(pfx_table_validate(&pfxt, 124, &pfx.prefix, 48, &res) == PFX_SUCCESS); + validate(&pfxt, 124, "2a01:4f8:131::", 48, BGP_PFXV_STATE_VALID); + + validate(&pfxt, 124, "2a01:4f8:131:15::", 56, BGP_PFXV_STATE_INVALID); + + assert(lrtr_ip_str_to_addr("1.0.4.0", &pfx.prefix) == 0); + pfx.min_len = 22; + pfx.max_len = 22; + pfx.asn = 56203; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, pfx.asn, "1.0.4.0", pfx.min_len, BGP_PFXV_STATE_VALID); + + assert(lrtr_ip_str_to_addr("1.8.1.0", &pfx.prefix) == 0); + pfx.min_len = 24; + pfx.max_len = 24; + pfx.asn = 38345; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, pfx.asn, "1.8.1.0", pfx.min_len, BGP_PFXV_STATE_VALID); + + assert(lrtr_ip_str_to_addr("1.8.8.0", &pfx.prefix) == 0); + pfx.min_len = 24; + pfx.max_len = 24; + pfx.asn = 38345; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, pfx.asn, "1.8.8.0", pfx.min_len, BGP_PFXV_STATE_VALID); + pfx_table_free(&pfxt); + + assert(lrtr_ip_str_to_addr("1.0.65.0", &pfx.prefix) == 0); + pfx.min_len = 18; + pfx.max_len = 18; + pfx.asn = 18144; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, pfx.asn, "1.0.65.0", pfx.min_len, BGP_PFXV_STATE_VALID); + pfx_table_free(&pfxt); + + assert(lrtr_ip_str_to_addr("10.0.0.0", &pfx.prefix) == 0); + pfx.min_len = 16; + pfx.max_len = 16; + pfx.asn = 123; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, pfx.asn, "10.0.0.0", pfx.min_len, BGP_PFXV_STATE_VALID); + validate(&pfxt, 124, "10.0.5.0", 24, BGP_PFXV_STATE_INVALID); + pfx_table_free(&pfxt); + + assert(lrtr_ip_str_to_addr("109.105.96.0", &pfx.prefix) == 0); + pfx.min_len = 19; + pfx.max_len = 19; + pfx.asn = 123; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 456, "109.105.128.0", 20, BGP_PFXV_STATE_NOT_FOUND); + pfx_table_free(&pfxt); + + assert(lrtr_ip_str_to_addr("190.57.224.0", &pfx.prefix) == 0); + pfx.min_len = 19; + pfx.max_len = 24; + pfx.asn = 123; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 123, "190.57.72.0", 21, BGP_PFXV_STATE_NOT_FOUND); + pfx_table_free(&pfxt); + + assert(lrtr_ip_str_to_addr("80.253.128.0", &pfx.prefix) == 0); + pfx.min_len = 19; + pfx.max_len = 19; + pfx.asn = 123; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 123, "80.253.144.0", 20, BGP_PFXV_STATE_INVALID); + + assert(lrtr_ip_str_to_addr("10.10.0.0", &pfx.prefix) == 0); + pfx.min_len = 16; + pfx.max_len = 16; + pfx.asn = 0; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + validate(&pfxt, 123, "10.10.0.0", 16, BGP_PFXV_STATE_INVALID); + + assert(lrtr_ip_str_to_addr("10.0.0.0", &pfx.prefix) == 0); + pfx.min_len = 8; + pfx.max_len = 15; + pfx.asn = 6; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + assert(lrtr_ip_str_to_addr("10.0.0.0", &pfx.prefix) == 0); + pfx.min_len = 8; + pfx.max_len = 15; + pfx.asn = 5; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + assert(lrtr_ip_str_to_addr("10.1.0.0", &pfx.prefix) == 0); + pfx.min_len = 16; + pfx.max_len = 16; + pfx.asn = 5; + assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); + + validate(&pfxt, 123, "10.1.0.0", 16, BGP_PFXV_STATE_INVALID); + + /* cleanup: free record and table */ + pfx_table_free(&pfxt); + printf("%s() successful\n", __func__); +} + +static void create_ip4_pfx_record(struct pfx_record *pfx, uint32_t asn, const char *ip, uint8_t min_mask_len, + uint8_t max_mask_len) +{ + pfx->asn = asn; + pfx->min_len = min_mask_len; + pfx->max_len = max_mask_len; + pfx->socket = (struct rtr_socket *)1; + assert(!lrtr_ip_str_to_addr(ip, &pfx->prefix)); +} + +static void add_ip4_pfx_record(struct pfx_table *pfxt, uint32_t asn, const char *ip, uint8_t min_mask_len, + uint8_t max_mask_len) +{ + struct pfx_record pfx; + enum pfxv_state val_res; + + create_ip4_pfx_record(&pfx, asn, ip, min_mask_len, max_mask_len); + + assert(pfx_table_add(pfxt, &pfx) == PFX_SUCCESS); + + assert(pfx_table_validate(pfxt, pfx.asn, &pfx.prefix, pfx.min_len, &val_res) == PFX_SUCCESS); + assert(val_res == BGP_PFXV_STATE_VALID); +} + +static void test_issue99(void) +{ + struct pfx_table pfxt; + + pfx_table_init(&pfxt, NULL); + + add_ip4_pfx_record(&pfxt, 200, "10.100.255.0", 24, 24); + add_ip4_pfx_record(&pfxt, 300, "255.0.0.0", 24, 24); + add_ip4_pfx_record(&pfxt, 400, "128.0.0.0", 1, 24); + + validate(&pfxt, 400, "255.0.0.0", 24, BGP_PFXV_STATE_VALID); + pfx_table_free(&pfxt); +} + +static void test_issue152(void) +{ + struct pfx_table pfxt; + struct pfx_record *records = calloc(6, sizeof(struct pfx_record)); + + pfx_table_init(&pfxt, NULL); + create_ip4_pfx_record(&records[0], 1, "89.18.183.0", 24, 24); + create_ip4_pfx_record(&records[1], 2, "109.164.0.0", 17, 25); + create_ip4_pfx_record(&records[2], 3, "185.131.60.0", 22, 24); + create_ip4_pfx_record(&records[3], 4, "185.146.28.0", 22, 22); + create_ip4_pfx_record(&records[4], 5, "212.5.51.0", 24, 24); + create_ip4_pfx_record(&records[5], 6, "213.175.86.0", 24, 24); + + for (size_t i = 0; i < 6; i++) + assert(pfx_table_add(&pfxt, &records[i]) == PFX_SUCCESS); + + for (size_t i = 0; i < 6; i++) + assert(pfx_table_remove(&pfxt, &records[i]) == PFX_SUCCESS); + + free(records); +} + +static void update_cb1(struct pfx_table *p __attribute__((unused)), const struct pfx_record rec, const bool added) +{ + char ip[INET6_ADDRSTRLEN]; + + if (added) + printf("+ "); + else + printf("- "); + lrtr_ip_addr_to_str(&rec.prefix, ip, sizeof(ip)); + printf("%-40s %3u - %3u %10u\n", ip, rec.min_len, rec.max_len, rec.asn); + + if (rec.asn == 1) + assert(!added); + if (rec.asn == 2) + assert(false); + if (rec.asn == 3) + assert(added); +} + +static void test_pfx_merge(void) +{ + struct pfx_table pfxt1; + struct pfx_table pfxt2; + struct pfx_record records[3]; + + pfx_table_init(&pfxt1, NULL); + pfx_table_init(&pfxt2, NULL); + + create_ip4_pfx_record(&records[0], 1, "1.1.1.1", 24, 24); + create_ip4_pfx_record(&records[1], 2, "2.2.2.2", 24, 24); + create_ip4_pfx_record(&records[2], 3, "3.3.3.3", 24, 24); + + printf("Adding to table one\n"); + assert(pfx_table_add(&pfxt1, &records[0]) == PFX_SUCCESS); + assert(pfx_table_add(&pfxt1, &records[1]) == PFX_SUCCESS); + printf("Adding to table two\n"); + assert(pfx_table_add(&pfxt2, &records[1]) == PFX_SUCCESS); + assert(pfx_table_add(&pfxt2, &records[2]) == PFX_SUCCESS); + + printf("Computing diff\n"); + pfxt1.update_fp = update_cb1; + pfxt2.update_fp = update_cb1; + pfx_table_notify_diff(&pfxt2, &pfxt1, (struct rtr_socket *)1); + pfxt1.update_fp = NULL; + pfxt2.update_fp = NULL; + + printf("Freeing table one\n"); + pfx_table_free(&pfxt1); + printf("Freeing table two\n"); + pfx_table_free(&pfxt2); +} + +int main(void) +{ + pfx_table_test(); + remove_src_test(); + mass_test(); + test_issue99(); + test_issue152(); + test_pfx_merge(); + + return EXIT_SUCCESS; +} diff --git a/tests/test_pfx_locks.c b/tests/test_pfx_locks.c new file mode 100644 index 0000000..1decf91 --- /dev/null +++ b/tests/test_pfx_locks.c @@ -0,0 +1,171 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/lib/ip.h" +#include "rtrlib/pfx/pfx.h" +#include "rtrlib/pfx/trie/trie-pfx.h" + +#include <arpa/inet.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +uint32_t min_i = 0xFF000000; +uint32_t max_i = 0xFFFFFFF0; + +/** + * @brief Add records to prefix table + */ +static void *rec_add(struct pfx_table *pfxt) +{ + const int tid = getpid(); + struct pfx_record rec; + + rec.min_len = 32; + rec.max_len = 32; + rec.prefix.ver = LRTR_IPV4; + rec.prefix.u.addr4.addr = 0; + + printf("Inserting %u records\n", (max_i - min_i) * 3); + for (uint32_t i = max_i; i >= min_i; i--) { + rec.min_len = 32; + rec.max_len = 32; + rec.socket = NULL; + rec.asn = tid % 2; + rec.prefix.u.addr4.addr = htonl(i); + rec.prefix.ver = LRTR_IPV4; + pfx_table_add(pfxt, &rec); + rec.asn = (tid % 2) + 1; + pfx_table_add(pfxt, &rec); + + rec.min_len = 128; + rec.max_len = 128; + rec.prefix.ver = LRTR_IPV6; + rec.prefix.u.addr6.addr[1] = min_i + 0xFFFFFFFF; + rec.prefix.u.addr6.addr[0] = htonl(i) + 0xFFFFFFFF; + pfx_table_add(pfxt, &rec); + usleep(rand() / (RAND_MAX / 20)); + } + + return NULL; +} + +/** + * @brief Validate records in prefix table + */ +static void *rec_val(struct pfx_table *pfxt) +{ + const int tid = getpid(); + struct pfx_record rec; + enum pfxv_state res; + + rec.min_len = 32; + rec.max_len = 32; + rec.prefix.ver = LRTR_IPV4; + rec.prefix.u.addr4.addr = 0; + printf("validating..\n"); + for (uint32_t i = max_i; i >= min_i; i--) { + rec.min_len = 32; + rec.max_len = 32; + rec.prefix.ver = LRTR_IPV4; + rec.prefix.u.addr4.addr = htonl(i); + pfx_table_validate(pfxt, (tid % 2), &rec.prefix, rec.min_len, &res); + pfx_table_validate(pfxt, (tid % 2) + 1, &rec.prefix, rec.min_len, &res); + + rec.min_len = 128; + rec.max_len = 128; + rec.prefix.ver = LRTR_IPV6; + rec.prefix.u.addr6.addr[1] = min_i + 0xFFFFFFFF; + rec.prefix.u.addr6.addr[0] = htonl(i) + 0xFFFFFFFF; + + pfx_table_validate(pfxt, (tid % 2) + 1, &rec.prefix, rec.min_len, &res); + usleep(rand() / (RAND_MAX / 20)); + } + + return NULL; +} + +/** + * @brief Delete records from prefix table + */ +static void *rec_del(struct pfx_table *pfxt) +{ + const int tid = getpid(); + struct pfx_record rec; + + rec.min_len = 32; + rec.max_len = 32; + rec.prefix.ver = LRTR_IPV4; + rec.prefix.u.addr4.addr = 0; + printf("removing records\n"); + for (uint32_t i = max_i; i >= min_i; i--) { + rec.socket = NULL; + rec.min_len = 32; + rec.max_len = 32; + rec.asn = tid % 2; + rec.prefix.ver = LRTR_IPV4; + rec.prefix.u.addr4.addr = htonl(i); + pfx_table_remove(pfxt, &rec); + rec.asn = (tid % 2) + 1; + pfx_table_remove(pfxt, &rec); + + rec.prefix.ver = LRTR_IPV6; + rec.min_len = 128; + rec.max_len = 128; + rec.prefix.u.addr6.addr[1] = min_i + 0xFFFFFFFF; + rec.prefix.u.addr6.addr[0] = htonl(i) + 0xFFFFFFFF; + + pfx_table_remove(pfxt, &rec); + usleep(rand() / (RAND_MAX / 20)); + } + printf("Done\n"); + + return NULL; +} + +/** + * @brief Test concurrent operations on pfx_table + * This test creates 15 (random) threads that add, delete, or validate huge + * numbers of entries in/to/from a pfx_table. + * + * NOTE: this test takes a (very) long time to complete, use with care! + */ +int main(void) +{ + unsigned int max_threads = 15; + struct pfx_table pfxt; + pthread_t threads[max_threads]; + + pfx_table_init(&pfxt, NULL); + srand(time(NULL)); + + for (unsigned int i = 0; i < max_threads; i++) { + int r = rand() / (RAND_MAX / 3); + + if (r == 0) + pthread_create(&threads[i], NULL, (void *(*)(void *))rec_add, &pfxt); + else if (r == 1) + pthread_create(&threads[i], NULL, (void *(*)(void *))rec_del, &pfxt); + else if (r == 2) + pthread_create(&threads[i], NULL, (void *(*)(void *))rec_val, &pfxt); + printf("Started Thread %d\n", i); + usleep(200); + } + for (unsigned int i = 0; i < max_threads; i++) { + pthread_join(threads[i], NULL); + printf("Thread %i returned\n", i); + } + + return EXIT_SUCCESS; +} diff --git a/tests/test_trie.c b/tests/test_trie.c new file mode 100644 index 0000000..e7ebb3c --- /dev/null +++ b/tests/test_trie.c @@ -0,0 +1,200 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/lib/log_private.h" +#include "rtrlib/lib/utils_private.h" +#include "rtrlib/pfx/trie/trie.c" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> + +/* + * @brief Test trie core operations such as add and remove + * This test validates core operations of the trie, + * namely insert, remove, lookup, and lookup_exact. + */ +static void trie_test(void) +{ + struct lrtr_ip_addr addr; + struct trie_node *result; + struct trie_node n1, n2, n3, n4; + unsigned int lvl = 0; + bool found; + + addr.ver = LRTR_IPV4; + + /* node 1 + * Tree after insert should be: + * 100.200.0.0/16 + */ + + n1.len = 16; + n1.lchild = NULL; + n1.rchild = NULL; + n1.parent = NULL; + n1.data = NULL; + lrtr_ip_str_to_addr("100.200.0.0", &n1.prefix); + addr = n1.prefix; + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "100.200.0.0")); + + lrtr_ip_str_to_addr("100.200.30.0", &addr); + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "100.200.0.0")); + + /* node 2 + * Tree after insert should be: + * 100.200.0.0/16 + * \ + * 132.200.0.0/16 + */ + n2.len = 16; + n2.lchild = NULL; + n2.rchild = NULL; + n2.parent = NULL; + n2.data = NULL; + lrtr_ip_str_to_addr("132.200.0.0", &n2.prefix); + trie_insert(&n1, &n2, 0); + lrtr_ip_str_to_addr("132.200.0.0", &addr); + lvl = 0; + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "132.200.0.0")); + assert(n1.rchild == &n2); + + /* node 3 + * Tree after insert should be: + * 100.200.0.0/16 + * / \ + * 101.200.0.0/16 132.200.0.0/16 + */ + n3.len = 16; + n3.lchild = NULL; + n3.rchild = NULL; + n3.parent = NULL; + n3.data = NULL; + + lrtr_ip_str_to_addr("101.200.0.0", &n3.prefix); + trie_insert(&n1, &n3, 0); + lrtr_ip_str_to_addr("101.200.0.0", &addr); + lvl = 0; + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "101.200.0.0")); + assert(n1.lchild == &n3); + + /* node 4 + * Tree after insert should be: + * 100.200.0.0/16 + * / \ + * 101.200.0.0/16 132.200.0.0/16 + * / + * 132.201.3.0/24 + */ + n4.len = 24; + n4.lchild = NULL; + n4.rchild = NULL; + n4.parent = NULL; + n4.data = NULL; + + lrtr_ip_str_to_addr("132.201.3.0", &n4.prefix); + trie_insert(&n1, &n4, 0); + lrtr_ip_str_to_addr("132.201.3.0", &addr); + lvl = 0; + result = trie_lookup(&n1, &addr, 24, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "132.201.3.0")); + + assert(lrtr_ip_str_cmp(&n1.prefix, "100.200.0.0")); + assert(n1.len == 16); + + /* verify tree structure */ + assert(lrtr_ip_str_cmp(&n1.lchild->prefix, "101.200.0.0")); + assert(n1.lchild->len == 16); + + assert(lrtr_ip_str_cmp(&n1.rchild->prefix, "132.200.0.0")); + assert(n1.rchild->len == 16); + + assert(lrtr_ip_str_cmp(&n1.rchild->lchild->prefix, "132.201.3.0")); + assert(n1.rchild->lchild->len == 24); + + lrtr_ip_str_to_addr("132.200.0.0", &addr); + lvl = 0; + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "132.200.0.0")); + + /* verify that a search for 132.200.3.0 returns 132.200/16 */ + lrtr_ip_str_to_addr("132.200.3.0", &addr); + lvl = 0; + result = trie_lookup(&n1, &addr, 16, &lvl); + assert(result); + assert(lrtr_ip_str_cmp(&result->prefix, "132.200.0.0")); + + /* verify no result for prefix 132.0.0.0/16 is found */ + lvl = 0; + lrtr_ip_str_to_addr("132.0.0.0", &addr); + result = trie_lookup_exact(&n1, &addr, 16, &lvl, &found); + assert(!found); + + /* verify trie_lookup_exact for prefix 132.201.3.0/24 is found */ + lvl = 0; + lrtr_ip_str_to_addr("132.201.3.0", &addr); + result = trie_lookup_exact(&n1, &addr, 24, &lvl, &found); + assert(found); + assert(lrtr_ip_str_cmp(&result->prefix, "132.201.3.0")); + + /* remove root->rchild + * Tree after remove should be: + * 100.200.0.0/16 + * / \ + * 101.200.0.0/16 132.201.3.0/24 + */ + lrtr_ip_str_to_addr("132.200.0.0", &addr); + result = trie_remove(&n1, &addr, 16, 0); + assert(result); + assert(lrtr_ip_str_cmp(&n1.prefix, "100.200.0.0")); + assert(lrtr_ip_str_cmp(&n1.lchild->prefix, "101.200.0.0")); + assert(lrtr_ip_str_cmp(&n1.rchild->prefix, "132.201.3.0")); + assert(!n1.rchild->lchild); + + /* remove root->lchild + * Tree after remove should be: + * 100.200.0.0/16 + * \ + * 132.201.3.0/24 + */ + lrtr_ip_str_to_addr("101.200.0.0", &addr); + result = trie_remove(&n1, &addr, 16, 0); + assert(result); + assert(lrtr_ip_str_cmp(&n1.rchild->prefix, "132.201.3.0")); + assert(!n1.lchild); + + /* remove root node + * Tree after remove should be: + * 132.201.3.0/24 + */ + lrtr_ip_str_to_addr("100.200.0.0", &addr); + result = trie_remove(&n1, &addr, 16, 0); + assert(lrtr_ip_str_cmp(&n1.prefix, "132.201.3.0")); + assert(result); + assert(!n1.lchild); + assert(!n1.rchild); +} + +int main(void) +{ + trie_test(); + printf("Test successful\n"); + return EXIT_SUCCESS; +} diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt new file mode 100644 index 0000000..29151dd --- /dev/null +++ b/tests/unittests/CMakeLists.txt @@ -0,0 +1,7 @@ + + +add_rtr_unit_test(test_packets_static test_packets_static.c rtrlib_static cmocka) +wrap_functions(test_packets_static lrtr_get_monotonic_time tr_send_all) + +add_rtr_unit_test(test_packets test_packets.c rtrlib_static cmocka) +wrap_functions(test_packets rtr_change_socket_state tr_send_all) diff --git a/tests/unittests/rtrlib_unittests.h b/tests/unittests/rtrlib_unittests.h new file mode 100644 index 0000000..4ea2866 --- /dev/null +++ b/tests/unittests/rtrlib_unittests.h @@ -0,0 +1,18 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website; http://rtrlib.realmv6.org/ + */ + +// clang-format off +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <cmocka.h> +// clang-format on + +#define UNUSED(x) (void)(x) diff --git a/tests/unittests/test_packets.c b/tests/unittests/test_packets.c new file mode 100644 index 0000000..be2e570 --- /dev/null +++ b/tests/unittests/test_packets.c @@ -0,0 +1,72 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib_unittests.h" +#include "test_packets.h" + +#include "rtrlib/rtr/packets_private.h" + +int __wrap_tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + UNUSED(socket); + UNUSED(pdu); + UNUSED(len); + UNUSED(timeout); + return (int)mock(); +} + +void __wrap_rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state) +{ + UNUSED(rtr_socket); + UNUSED(new_state); +} + +static void test_rtr_send_reset_query(void **state) +{ + struct rtr_socket socket; + + UNUSED(state); + + socket.connection_state_fp = NULL; + + will_return(__wrap_tr_send_all, 0); + assert_int_equal(rtr_send_reset_query(&socket), RTR_ERROR); + + will_return(__wrap_tr_send_all, 10); + assert_int_equal(rtr_send_reset_query(&socket), RTR_SUCCESS); +} + +static void test_rtr_change_socket_state(void **state) +{ + struct rtr_socket socket; + + UNUSED(state); + + socket.connection_state_fp = NULL; + socket.state = RTR_RESET; + + __real_rtr_change_socket_state(&socket, RTR_SYNC); + assert_int_equal(socket.state, RTR_SYNC); + + __real_rtr_change_socket_state(&socket, RTR_SYNC); + assert_int_equal(socket.state, RTR_SYNC); + + __real_rtr_change_socket_state(&socket, RTR_SHUTDOWN); + assert_int_equal(socket.state, RTR_SHUTDOWN); + + __real_rtr_change_socket_state(&socket, RTR_ESTABLISHED); + assert_int_equal(socket.state, RTR_SHUTDOWN); +} + +int main(void) +{ + const struct CMUnitTest tests[] = {cmocka_unit_test(test_rtr_send_reset_query), + cmocka_unit_test(test_rtr_change_socket_state)}; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/unittests/test_packets.h b/tests/unittests/test_packets.h new file mode 100644 index 0000000..2fe7f9c --- /dev/null +++ b/tests/unittests/test_packets.h @@ -0,0 +1,14 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website; http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/rtr/rtr.h" + +void __real_rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state); +int __wrap_tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); +void __wrap_rtr_change_socket_state(struct rtr_socket *rtr_socket, const enum rtr_socket_state new_state); diff --git a/tests/unittests/test_packets_static.c b/tests/unittests/test_packets_static.c new file mode 100644 index 0000000..b8589d7 --- /dev/null +++ b/tests/unittests/test_packets_static.c @@ -0,0 +1,444 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website; http://rtrlib.realmv6.org/ + */ + +#include "rtrlib_unittests.h" +#include "test_packets_static.h" + +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtr/packets.c" +#include "rtrlib/rtr_mgr_private.h" + +int __wrap_lrtr_get_monotonic_time(time_t *seconds) +{ + UNUSED(seconds); + return mock_type(int); +} + +int __wrap_tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout) +{ + check_expected(socket); + check_expected(pdu); + check_expected(len); + check_expected(timeout); + return (int)mock(); +} + +static int cmp_serial_in_error_pdu(const LargestIntegralType test, const LargestIntegralType reference) +{ + struct pdu_error *test_pdu = (struct pdu_error *)test; + struct pdu_serial_query *reference_pdu = (struct pdu_serial_query *)reference; + struct pdu_serial_query *encapsulated_pdu = (struct pdu_serial_query *)test_pdu->rest; + + if (encapsulated_pdu->ver != reference_pdu->ver || encapsulated_pdu->type != reference_pdu->type || + encapsulated_pdu->session_id != reference_pdu->session_id || encapsulated_pdu->len != reference_pdu->len || + encapsulated_pdu->sn != reference_pdu->sn) { + return 0; + } + + return 1; +} + +static int cmp_header_in_error_pdu(const LargestIntegralType test, const LargestIntegralType reference) +{ + struct pdu_error *test_pdu = (struct pdu_error *)test; + struct pdu_serial_query *reference_pdu = (struct pdu_serial_query *)reference; + struct pdu_serial_query *encapsulated_pdu = (struct pdu_serial_query *)test_pdu->rest; + + if (encapsulated_pdu->ver != reference_pdu->ver || encapsulated_pdu->type != reference_pdu->type || + encapsulated_pdu->session_id != reference_pdu->session_id || encapsulated_pdu->len != reference_pdu->len) { + return 0; + } + + return 1; +} + +static void test_set_last_update(void **state) +{ + struct rtr_socket socket; + + UNUSED(state); + + socket.connection_state_fp = NULL; + + will_return(__wrap_lrtr_get_monotonic_time, RTR_ERROR); + assert_int_equal(rtr_set_last_update(&socket), RTR_ERROR); + + will_return(__wrap_lrtr_get_monotonic_time, RTR_SUCCESS); + assert_int_equal(rtr_set_last_update(&socket), RTR_SUCCESS); +} + +static void test_rtr_get_pdu_type(void **state) +{ + struct pdu_header pdu; + + UNUSED(state); + + pdu.type = SERIAL_NOTIFY; + assert_int_equal(rtr_get_pdu_type(&pdu), SERIAL_NOTIFY); + + pdu.type = SERIAL_QUERY; + assert_int_equal(rtr_get_pdu_type(&pdu), SERIAL_QUERY); + + pdu.type = RESET_QUERY; + assert_int_equal(rtr_get_pdu_type(&pdu), RESET_QUERY); + + pdu.type = CACHE_RESPONSE; + assert_int_equal(rtr_get_pdu_type(&pdu), CACHE_RESPONSE); + + pdu.type = RESERVED; + assert_int_equal(rtr_get_pdu_type(&pdu), RESERVED); + + pdu.type = IPV6_PREFIX; + assert_int_equal(rtr_get_pdu_type(&pdu), IPV6_PREFIX); + + pdu.type = EOD; + assert_int_equal(rtr_get_pdu_type(&pdu), EOD); + + pdu.type = CACHE_RESET; + assert_int_equal(rtr_get_pdu_type(&pdu), CACHE_RESET); + + pdu.type = ROUTER_KEY; + assert_int_equal(rtr_get_pdu_type(&pdu), ROUTER_KEY); + + pdu.type = ERROR; + assert_int_equal(rtr_get_pdu_type(&pdu), ERROR); +} + +static void test_pdu_to_network_byte_order(void **state) +{ + struct pdu_serial_query pdu; + + UNUSED(state); + + pdu.ver = 0; + pdu.type = SERIAL_QUERY; + pdu.session_id = 0x32A5; + pdu.len = 0xC; + pdu.sn = 0xCC56E9BB; + + rtr_pdu_to_network_byte_order(&pdu); + + assert_int_equal(pdu.ver, 0); + assert_int_equal(pdu.type, SERIAL_QUERY); + assert_int_equal(pdu.session_id, 0xA532); + assert_int_equal(pdu.len, 0x0C000000); + assert_int_equal(pdu.sn, 0xBBE956CC); +} + +static void test_pdu_to_host_byte_order(void **state) +{ + struct pdu_serial_notify pdu_serial; + struct pdu_end_of_data_v1 pdu_eod; + + UNUSED(state); + + pdu_serial.ver = 1; + pdu_serial.type = SERIAL_NOTIFY; + pdu_serial.session_id = 0xDDFF; + pdu_serial.len = 0xC; + pdu_serial.sn = 0xDF; + + rtr_pdu_footer_to_host_byte_order(&pdu_serial); + rtr_pdu_header_to_host_byte_order(&pdu_serial); + + assert_int_equal(pdu_serial.ver, 1); + assert_int_equal(pdu_serial.type, SERIAL_NOTIFY); + assert_int_equal(pdu_serial.len, 0xC000000); + assert_int_equal(pdu_serial.sn, 0xDF000000); + + pdu_eod.ver = 1; + pdu_eod.type = EOD; + pdu_eod.session_id = 0xFDDF; + pdu_eod.len = 0x18; + pdu_eod.sn = 0xFEDCBA; + pdu_eod.refresh_interval = 0xAF; + pdu_eod.retry_interval = 0xDC; + pdu_eod.expire_interval = 0xCCF; + + rtr_pdu_header_to_host_byte_order(&pdu_eod); + rtr_pdu_footer_to_host_byte_order(&pdu_eod); + + assert_int_equal(pdu_eod.ver, 1); + assert_int_equal(pdu_eod.type, EOD); + assert_int_equal(pdu_eod.session_id, 0xDFFD); + assert_int_equal(pdu_eod.len, 0x18000000); + assert_int_equal(pdu_eod.sn, 0xBADCFE00); + assert_int_equal(pdu_eod.refresh_interval, 0xAF000000); + assert_int_equal(pdu_eod.retry_interval, 0xDC000000); + assert_int_equal(pdu_eod.expire_interval, 0xCF0C0000); +} + +static void test_rtr_pdu_check_size(void **state) +{ + struct pdu_header pdu; + struct pdu_error *error = malloc(30); + + UNUSED(state); + + memset(error, 0, 30); + + pdu.type = SERIAL_NOTIFY; + pdu.len = 20; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 12; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = CACHE_RESPONSE; + pdu.len = 5; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 8; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = IPV4_PREFIX; + pdu.len = 25; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 20; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = IPV6_PREFIX; + pdu.len = 15; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 32; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = CACHE_RESET; + pdu.len = 10; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 8; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = SERIAL_QUERY; + pdu.len = 14; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 12; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = RESET_QUERY; + pdu.len = 10; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 8; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = RESERVED; + pdu.len = 0; + assert_false(rtr_pdu_check_size(&pdu)); + + pdu.type = EOD; + pdu.ver = RTR_PROTOCOL_VERSION_1; + pdu.len = 12; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 24; + assert_true(rtr_pdu_check_size(&pdu)); + pdu.ver = RTR_PROTOCOL_VERSION_0; + pdu.len = 18; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 12; + assert_true(rtr_pdu_check_size(&pdu)); + + pdu.type = ROUTER_KEY; + pdu.len = 124; + assert_false(rtr_pdu_check_size(&pdu)); + pdu.len = 123; + assert_true(rtr_pdu_check_size(&pdu)); + + /* Test error pdu size checks */ + error->type = ERROR; + error->len = 14; + error->len_enc_pdu = 0; + assert_false(rtr_pdu_check_size((struct pdu_header *)error)); + error->len = 20; + error->len_enc_pdu = 0x8000000; + assert_false(rtr_pdu_check_size((struct pdu_header *)error)); + error->len = 24; + error->rest[11] = 0xA; + assert_false(rtr_pdu_check_size((struct pdu_header *)error)); +} + +static void test_rtr_send_error_pdu(void **state) +{ + struct pdu_serial_query pdu_serial, pdu_serial_network; + struct rtr_socket socket; + int ret; + + UNUSED(state); + + pdu_serial.ver = 1; + pdu_serial.type = SERIAL_NOTIFY; + pdu_serial.session_id = 0xDDFF; + pdu_serial.len = sizeof(struct pdu_serial_query); + pdu_serial.sn = 0xDF; + + memcpy(&pdu_serial_network, &pdu_serial, sizeof(struct pdu_serial_query)); + rtr_pdu_to_network_byte_order(&pdu_serial_network); + + expect_any_count(__wrap_tr_send_all, socket, 1); + expect_any_count(__wrap_tr_send_all, len, 1); + expect_any_count(__wrap_tr_send_all, timeout, 1); + expect_check(__wrap_tr_send_all, pdu, cmp_serial_in_error_pdu, &pdu_serial_network); + will_return(__wrap_tr_send_all, sizeof(pdu_serial) + sizeof(struct pdu_error)); + + ret = rtr_send_error_pdu_from_host(&socket, &pdu_serial, pdu_serial.len, INTERNAL_ERROR, NULL, 0); + assert_int_equal(ret, RTR_SUCCESS); + + expect_any_count(__wrap_tr_send_all, socket, 1); + expect_any_count(__wrap_tr_send_all, len, 1); + expect_any_count(__wrap_tr_send_all, timeout, 1); + expect_check(__wrap_tr_send_all, pdu, cmp_serial_in_error_pdu, &pdu_serial_network); + will_return(__wrap_tr_send_all, sizeof(pdu_serial_network) + sizeof(struct pdu_error)); + + ret = rtr_send_error_pdu_from_network(&socket, &pdu_serial_network, pdu_serial.len, INTERNAL_ERROR, NULL, 0); + assert_int_equal(ret, RTR_SUCCESS); + + expect_any_count(__wrap_tr_send_all, socket, 1); + expect_any_count(__wrap_tr_send_all, len, 1); + expect_any_count(__wrap_tr_send_all, timeout, 1); + expect_check(__wrap_tr_send_all, pdu, cmp_header_in_error_pdu, &pdu_serial_network); + will_return(__wrap_tr_send_all, sizeof(pdu_serial) + sizeof(struct pdu_error)); + + ret = rtr_send_error_pdu_from_host(&socket, &pdu_serial, sizeof(struct pdu_header), INTERNAL_ERROR, NULL, 0); + assert_int_equal(ret, RTR_SUCCESS); + + ret = rtr_send_error_pdu_from_host(&socket, &pdu_serial, 2, INTERNAL_ERROR, NULL, 0); + assert_int_equal(ret, RTR_ERROR); +} + +static void test_rtr_pdu_check_interval(void **state) +{ + UNUSED(state); + + struct rtr_socket rtr_socket; + struct pdu_end_of_data_v1 pdu_eod; + + int retval; + + rtr_socket.refresh_interval = 0; + rtr_socket.retry_interval = 0; + rtr_socket.expire_interval = 0; + + pdu_eod.refresh_interval = 1; + pdu_eod.retry_interval = 2; + pdu_eod.expire_interval = 601; + + /* test appliance of interval values to the rtr_socket */ + apply_interval_value(&rtr_socket, pdu_eod.refresh_interval, RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(rtr_socket.refresh_interval, pdu_eod.refresh_interval); + + apply_interval_value(&rtr_socket, pdu_eod.retry_interval, RTR_INTERVAL_TYPE_RETRY); + assert_int_equal(rtr_socket.retry_interval, pdu_eod.retry_interval); + + apply_interval_value(&rtr_socket, pdu_eod.expire_interval, RTR_INTERVAL_TYPE_EXPIRATION); + assert_int_equal(rtr_socket.expire_interval, pdu_eod.expire_interval); + + /* test checks that determine if value is inside range */ + retval = rtr_check_interval_range(pdu_eod.refresh_interval, RTR_REFRESH_MIN, RTR_REFRESH_MAX); + assert_int_equal(retval, RTR_INSIDE_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.retry_interval, RTR_RETRY_MIN, RTR_RETRY_MAX); + assert_int_equal(retval, RTR_INSIDE_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.expire_interval, RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX); + assert_int_equal(retval, RTR_INSIDE_INTERVAL_RANGE); + + /* test checks that determine if value is below range */ + pdu_eod.refresh_interval = RTR_REFRESH_MIN - 1; + pdu_eod.retry_interval = RTR_RETRY_MIN - 1; + pdu_eod.expire_interval = RTR_EXPIRATION_MIN - 1; + + retval = rtr_check_interval_range(pdu_eod.refresh_interval, RTR_REFRESH_MIN, RTR_REFRESH_MAX); + assert_int_equal(retval, RTR_BELOW_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.retry_interval, RTR_RETRY_MIN, RTR_RETRY_MAX); + assert_int_equal(retval, RTR_BELOW_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.expire_interval, RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX); + assert_int_equal(retval, RTR_BELOW_INTERVAL_RANGE); + + /* test checks that determine if value is above range */ + pdu_eod.refresh_interval = RTR_REFRESH_MAX + 1; + pdu_eod.retry_interval = RTR_RETRY_MAX + 1; + pdu_eod.expire_interval = RTR_EXPIRATION_MAX + 1; + + retval = rtr_check_interval_range(pdu_eod.refresh_interval, RTR_REFRESH_MIN, RTR_REFRESH_MAX); + assert_int_equal(retval, RTR_ABOVE_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.retry_interval, RTR_RETRY_MIN, RTR_RETRY_MAX); + assert_int_equal(retval, RTR_ABOVE_INTERVAL_RANGE); + + retval = rtr_check_interval_range(pdu_eod.expire_interval, RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX); + assert_int_equal(retval, RTR_ABOVE_INTERVAL_RANGE); + + /* test the different interval options the user can choose */ + rtr_socket.refresh_interval = 0; + pdu_eod.refresh_interval = 42; + retval = rtr_check_interval_option(&rtr_socket, RTR_INTERVAL_MODE_ACCEPT_ANY, pdu_eod.refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(retval, RTR_SUCCESS); + assert_int_equal(rtr_socket.refresh_interval, pdu_eod.refresh_interval); + + rtr_socket.refresh_interval = 0; + pdu_eod.refresh_interval = RTR_REFRESH_MAX + 1; + retval = rtr_check_interval_option(&rtr_socket, RTR_INTERVAL_MODE_DEFAULT_MIN_MAX, pdu_eod.refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(retval, RTR_SUCCESS); + assert_int_equal(rtr_socket.refresh_interval, RTR_REFRESH_MAX); + + rtr_socket.refresh_interval = 0; + pdu_eod.refresh_interval = RTR_REFRESH_MIN - 1; + retval = rtr_check_interval_option(&rtr_socket, RTR_INTERVAL_MODE_DEFAULT_MIN_MAX, pdu_eod.refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(retval, RTR_SUCCESS); + assert_int_equal(rtr_socket.refresh_interval, RTR_REFRESH_MIN); + + rtr_socket.refresh_interval = 42; + pdu_eod.refresh_interval = RTR_REFRESH_MIN - 1; + retval = rtr_check_interval_option(&rtr_socket, RTR_INTERVAL_MODE_IGNORE_ON_FAILURE, pdu_eod.refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(retval, RTR_SUCCESS); + assert_int_equal(rtr_socket.refresh_interval, 42); + + rtr_socket.refresh_interval = 0; + pdu_eod.refresh_interval = RTR_REFRESH_MAX + 1; + retval = rtr_check_interval_option(&rtr_socket, RTR_INTERVAL_MODE_ACCEPT_ANY, pdu_eod.refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + assert_int_equal(retval, RTR_SUCCESS); + assert_int_equal(rtr_socket.refresh_interval, RTR_REFRESH_MAX + 1); +} + +static void test_set_interval_option(void **state) +{ + UNUSED(state); + + struct rtr_socket rtr_socket; + + rtr_set_interval_mode(&rtr_socket, RTR_INTERVAL_MODE_IGNORE_ANY); + assert_int_equal(rtr_get_interval_mode(&rtr_socket), RTR_INTERVAL_MODE_IGNORE_ANY); + + rtr_set_interval_mode(&rtr_socket, RTR_INTERVAL_MODE_ACCEPT_ANY); + assert_int_equal(rtr_get_interval_mode(&rtr_socket), RTR_INTERVAL_MODE_ACCEPT_ANY); + + rtr_set_interval_mode(&rtr_socket, RTR_INTERVAL_MODE_DEFAULT_MIN_MAX); + assert_int_equal(rtr_get_interval_mode(&rtr_socket), RTR_INTERVAL_MODE_DEFAULT_MIN_MAX); + + rtr_set_interval_mode(&rtr_socket, RTR_INTERVAL_MODE_IGNORE_ON_FAILURE); + assert_int_equal(rtr_get_interval_mode(&rtr_socket), RTR_INTERVAL_MODE_IGNORE_ON_FAILURE); + + rtr_set_interval_mode(&rtr_socket, 4); + assert(rtr_get_interval_mode(&rtr_socket) != 4); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_set_last_update), cmocka_unit_test(test_rtr_get_pdu_type), + cmocka_unit_test(test_pdu_to_network_byte_order), cmocka_unit_test(test_pdu_to_host_byte_order), + cmocka_unit_test(test_rtr_pdu_check_size), cmocka_unit_test(test_rtr_send_error_pdu), + cmocka_unit_test(test_rtr_pdu_check_interval), cmocka_unit_test(test_set_interval_option), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/unittests/test_packets_static.h b/tests/unittests/test_packets_static.h new file mode 100644 index 0000000..58704ba --- /dev/null +++ b/tests/unittests/test_packets_static.h @@ -0,0 +1,14 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website; http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/transport/transport_private.h" + +int __wrap_lrtr_get_monotonic_time(time_t *seconds); + +int __wrap_tr_send_all(const struct tr_socket *socket, const void *pdu, const size_t len, const time_t timeout); diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt new file mode 100644 index 0000000..c61ca38 --- /dev/null +++ b/third-party/CMakeLists.txt @@ -0,0 +1,4 @@ +set(tommyds "${CMAKE_CURRENT_SOURCE_DIR}/tommyds/tommy.c" PARENT_SCOPE) + +set(mustach "${CMAKE_CURRENT_SOURCE_DIR}/mustach/mustach.c" PARENT_SCOPE) +set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/mustach/mustach.c" PROPERTIES COMPILE_FLAGS "-DNO_EXTENSION_FOR_MUSTACH") diff --git a/third-party/mustach.h b/third-party/mustach.h new file mode 100644 index 0000000..b20a61a --- /dev/null +++ b/third-party/mustach.h @@ -0,0 +1,15 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +/* fix missing include in the mustach header by including it first */ + +// clang-format off +#include <stdio.h> +#include "third-party/mustach/mustach.h" +// clang-format on diff --git a/third-party/mustach/AUTHORS b/third-party/mustach/AUTHORS new file mode 100644 index 0000000..82d2fee --- /dev/null +++ b/third-party/mustach/AUTHORS @@ -0,0 +1,15 @@ +Main author: + José Bollo <jobol@nonadev.net> + +Contributors: + Harold L Marzan + Atlas + Sami Kerola + Tomasz Sieprawski + Lailton Fernando Mariano + +Thanks to issue submitters: + Thierry Fournier + Dante Torres + Mark Bucciarelli + Paul Wisehart diff --git a/third-party/mustach/LICENSE-2.0.txt b/third-party/mustach/LICENSE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/third-party/mustach/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third-party/mustach/README.md b/third-party/mustach/README.md new file mode 100644 index 0000000..026fc87 --- /dev/null +++ b/third-party/mustach/README.md @@ -0,0 +1,117 @@ +Introduction to Mustach +======================= + +`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache") template library. + +The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). + +The best way to use mustach is to copy the files **mustach.h** and **mustach.c** +directly into your project and use it. + +The current source files are: + +- **mustach.c** core implementation of mustache in C +- **mustach.h** header file for core definitions +- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c) +- **mustach-json-c.h** header file for using the tiny JSON wrapper +- **mustach-tool.c** simple tool for applying template files to a JSON file + +The file **mustach-json-c.c** is the main example of use of **mustach** core +and it is also a practical implementation that can be used. It uses the library +json-c. (NOTE for Mac OS: available through homebrew). + +HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). + +The tool **mustach** is build using `make`, its usage is: + + mustach json template [template]... + +It then outputs the result of applying the templates files to the JSON file. + +Portability +=========== + +Some system does not provide *open_memstream*. In that case, tell your +prefered compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. +Example: + + gcc -DNO_OPEN_MEMSTREAM + +Extensions +========== + +By default, the current implementation provides the following extensions: + +Explicit Substitution +--------------------- + +This is a core extension implemented in file **mustach.c**. + +In somecases the name of the key used for substition begins with a +character reserved for mustach: one of '#', '^', '/', '&', '{', '>' and '='. +This extension introduces the special character ':' to explicitly +tell mustach to just substitute the value. So ':' becomes a new special +character. + +Value Testing +------------- + +This is a tool extension implmented in file **mustach-json-c.c**. + +This extension allows you to test the value of the selected key. +It is allowed to write key=value (matching test) or key=!value +(not matching test) in any query. + +Removing Extensions +------------------- + +When compiling mustach.c or mustach-json-c.c, +extensions can be removed by defining macros +using option -D. + +The possible macros are: + +- `NO_COLON_EXTENSION_FOR_MUSTACH` + + This macro remove the ability to use colon (:) + as explicit command for variable substituion. + This extension allows to have name starting + with one of the mustach character :#^/&{=< + +- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` + + This macro allows the program to check the whether + the actual value is equal to an expected value. + This is useful in `{{#key=val}}` or `{{^key=val}}` + with the corresponding `{{/key=val}}`. + It can also be used in `{{key=val}}` but this + doesn't seem to be useful. + +- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` + + This macro removes the possible use of JSON pointers. + JSON pointers are defined in IETF RFC 6901. + If not set, any key starting with "/" is a JSON pointer. + This implies to use the colon to introduce keys. + So `NO_COLON_EXTENSION_FOR_MUSTACH` implies + `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`. + A special escaping is used for "=" signs when + values comparison is enabled: "~=" leaves "=" in the key. + +- `NO_ALLOW_EMPTY_TAG` + + Generate the error MUSTACH_ERROR_EMPTY_TAG automatically. + +- NO_OBJECT_ITERATION_FOR_MUSTACH + + Disable the object iteration extension. That extension allows + to iterate over the keys of an object. The iteration on object + is selected by using the selector `{{#key.*}}`. In the context + of iterating over object keys, the single key `{{*}}` returns the + key and `{{.}}` returns the value. + +- `NO_EXTENSION_FOR_MUSTACH` + + This macro disables any current or future + extensions. + diff --git a/third-party/mustach/mustach.c b/third-party/mustach/mustach.c new file mode 100644 index 0000000..830e72c --- /dev/null +++ b/third-party/mustach/mustach.c @@ -0,0 +1,327 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include "mustach.h" + +#define NAME_LENGTH_MAX 1024 +#define DEPTH_MAX 256 + +#if !defined(NO_OPEN_MEMSTREAM) +static FILE *memfile_open(char **buffer, size_t *size) +{ + return open_memstream(buffer, size); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + free(*buffer); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + + /* adds terminating null */ + rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; + fclose(file); + if (rc == 0) + /* removes terminating null of the length */ + (*size)--; + else { + free(*buffer); + *buffer = NULL; + *size = 0; + } + return rc; +} +#else +static FILE *memfile_open(char **buffer, size_t *size) +{ + return tmpfile(); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + size_t s; + char *b; + + s = (size_t)ftell(file); + b = malloc(s + 1); + if (b == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + s = 0; + } else { + rewind(file); + if (1 == fread(b, s, 1, file)) { + rc = 0; + b[s] = 0; + } else { + rc = MUSTACH_ERROR_SYSTEM; + free(b); + b = NULL; + s = 0; + } + } + *buffer = b; + *size = s; + return rc; +} +#endif + +static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result) +{ + int rc; + FILE *file; + size_t size; + + *result = NULL; + file = memfile_open(result, &size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = itf->put(closure, name, 0, file); + if (rc < 0) + memfile_abort(file, result, &size); + else + rc = memfile_close(file, result, &size); + } + return rc; +} + +static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr) +{ + char name[NAME_LENGTH_MAX + 1], *partial, c, *tmp; + const char *beg, *term; + struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX]; + size_t oplen, cllen, len, l; + int depth, rc, emit; + + emit = 1; + oplen = strlen(opstr); + cllen = strlen(clstr); + depth = 0; + for(;;) { + beg = strstr(template, opstr); + if (beg == NULL) { + /* no more mustach */ + if (emit) + fwrite(template, strlen(template), 1, file); + return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0; + } + if (emit) + fwrite(template, (size_t)(beg - template), 1, file); + beg += oplen; + term = strstr(beg, clstr); + if (term == NULL) + return MUSTACH_ERROR_UNEXPECTED_END; + template = term + cllen; + len = (size_t)(term - beg); + c = *beg; + switch(c) { + case '!': + case '=': + break; + case '{': + for (l = 0 ; clstr[l] == '}' ; l++); + if (clstr[l]) { + if (!len || beg[len-1] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + len--; + } else { + if (term[l] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + template++; + } + c = '&'; + /*@fallthrough@*/ + case '^': + case '#': + case '/': + case '&': + case '>': +#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH) + case ':': +#endif + beg++; len--; + default: + while (len && isspace(beg[0])) { beg++; len--; } + while (len && isspace(beg[len-1])) len--; +#if defined(NO_EXTENSION_FOR_MUSTACH) || defined(NO_ALLOW_EMPTY_TAG) + if (len == 0) + return MUSTACH_ERROR_EMPTY_TAG; +#endif + if (len > NAME_LENGTH_MAX) + return MUSTACH_ERROR_TAG_TOO_LONG; + memcpy(name, beg, len); + name[len] = 0; + break; + } + switch(c) { + case '!': + /* comment */ + /* nothing to do */ + break; + case '=': + /* defines separators */ + if (len < 5 || beg[len - 1] != '=') + return MUSTACH_ERROR_BAD_SEPARATORS; + beg++; + len -= 2; + for (l = 0; l < len && !isspace(beg[l]) ; l++); + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + oplen = l; + tmp = alloca(oplen + 1); + memcpy(tmp, beg, oplen); + tmp[oplen] = 0; + opstr = tmp; + while (l < len && isspace(beg[l])) l++; + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + cllen = len - l; + tmp = alloca(cllen + 1); + memcpy(tmp, beg + l, cllen); + tmp[cllen] = 0; + clstr = tmp; + break; + case '^': + case '#': + /* begin section */ + if (depth == DEPTH_MAX) + return MUSTACH_ERROR_TOO_DEEP; + rc = emit; + if (rc) { + rc = itf->enter(closure, name); + if (rc < 0) + return rc; + } + + if (template[0] == '\n') + ++template; + + stack[depth].name = beg; + stack[depth].again = template; + stack[depth].length = len; + stack[depth].emit = emit; + stack[depth].entered = rc; + if ((c == '#') == (rc == 0)) + emit = 0; + depth++; + break; + case '/': + /* end section */ + if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) + return MUSTACH_ERROR_CLOSING; + rc = emit && stack[depth].entered ? itf->next(closure) : 0; + if (rc < 0) + return rc; + if (rc) { + template = stack[depth++].again; + } else { + emit = stack[depth].emit; + if (emit && stack[depth].entered) + itf->leave(closure); + } + break; + case '>': + /* partials */ + if (emit) { + rc = getpartial(itf, closure, name, &partial); + if (rc == 0) { + rc = process(partial, itf, closure, file, opstr, clstr); + free(partial); + } + if (rc < 0) + return rc; + } + break; + default: + /* replacement */ + if (emit) { + rc = itf->put(closure, name, c != '&', file); + if (rc < 0) + return rc; + } + break; + } + } +} + +int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +{ + int rc = itf->start ? itf->start(closure) : 0; + if (rc == 0) + rc = process(template, itf, closure, file, "{{", "}}"); + return rc; +} + +int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +{ + int rc; + FILE *file; + + file = fdopen(fd, "w"); + if (file == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + } else { + rc = fmustach(template, itf, closure, file); + fclose(file); + } + return rc; +} + +int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + int rc; + FILE *file; + size_t s; + + *result = NULL; + if (size == NULL) + size = &s; + file = memfile_open(result, size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = fmustach(template, itf, closure, file); + if (rc < 0) + memfile_abort(file, result, size); + else + rc = memfile_close(file, result, size); + } + return rc; +} + diff --git a/third-party/mustach/mustach.h b/third-party/mustach/mustach.h new file mode 100644 index 0000000..32288a1 --- /dev/null +++ b/third-party/mustach/mustach.h @@ -0,0 +1,120 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_h_included_ +#define _mustach_h_included_ + +/** + * mustach_itf - interface for callbacks + * + * All of this function should return a negative value to stop + * the mustache processing. The returned negative value will be + * then returned to the caller of mustach as is. + * + * The functions enter and next should return 0 or 1. + * + * All other functions should normally return 0. If it returns + * a negative value, it means an error that stop the process + * and that is reported to the caller. + * + * @start: Starts the mustach processing of the closure + * 'start' is optional (can be NULL) + * + * @put: Writes the value of 'name' to 'file' with 'escape' or not + * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be + * the empty string. In that later case an implemntation can + * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. + * + * @enter: Enters the section of 'name' if possible. + * Musts return 1 if entered or 0 if not entered. + * When 1 is returned, the function 'leave' will always be called. + * Conversely 'leave' is never called when enter returns 0 or + * a negative value. + * When 1 is returned, the function must activate the first + * item of the section. + * + * @next: Activates the next item of the section if it exists. + * Musts return 1 when the next item is activated. + * Musts return 0 when there is no item to activate. + * + * @leave: Leaves the last entered section + */ +struct mustach_itf { + int (*start)(void *closure); + int (*put)(void *closure, const char *name, int escape, FILE *file); + int (*enter)(void *closure, const char *name); + int (*next)(void *closure); + int (*leave)(void *closure); +}; + +#define MUSTACH_OK 0 +#define MUSTACH_ERROR_SYSTEM -1 +#define MUSTACH_ERROR_UNEXPECTED_END -2 +#define MUSTACH_ERROR_EMPTY_TAG -3 +#define MUSTACH_ERROR_TAG_TOO_LONG -4 +#define MUSTACH_ERROR_BAD_SEPARATORS -5 +#define MUSTACH_ERROR_TOO_DEEP -6 +#define MUSTACH_ERROR_CLOSING -7 +#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 + +/* compatibility with older bad name */ +#define MUSTACH_ERROR_TOO_DEPTH MUSTACH_ERROR_TOO_DEEP + +/** + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); + +/** + * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd); + +/** + * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size); + +#endif + diff --git a/third-party/tommyds/AUTHORS b/third-party/tommyds/AUTHORS new file mode 100644 index 0000000..23071a0 --- /dev/null +++ b/third-party/tommyds/AUTHORS @@ -0,0 +1,9 @@ +TommyDS AUTHORS +=============== + +The author of TommyDS is Andrea Mazzoleni. + +You can contact me sending an email at: + + amadvance@gmail.com + diff --git a/third-party/tommyds/HISTORY b/third-party/tommyds/HISTORY new file mode 100644 index 0000000..ae56bc5 --- /dev/null +++ b/third-party/tommyds/HISTORY @@ -0,0 +1,90 @@ +TommyDS HISTORY +=============== + +2.2 2018/02 +=========== + * Removed tommy_list_remove_head_not_empty() as not used and wrongly + implemented [Daniel Roethlisberger]. + +2.1 2016/11 +=========== + * Added a new hash function for strings: tommy_strhash_u32(). + * Added a new tree implementation. It's not intended to be fast but useful if + you need elements in order. + * Fixed some references to TOMMY_ARRAYBLKOF_SIZE, where instead + TOMMY_ARRAYBLK_SIZE was incorrectly used [Rocco]. + +2.0 2014/12 +=========== + * Fixed a Segmentation Fault bug in the trie_inplace container when inserting + duplicate elements. + * Faster array and hashlin implementation when accessing elements. + * Added new hashtable functions to iterate over all the elements. + * Added a new tommy_calloc() function used for allocating initialized memory. + If you redefined tommy_malloc(), likely you have to redefine also tommy_calloc(). + * Reached 100% code coverage in the regression test. + * Different source code organization. + * Added benchmark comparison with Binary Search Tesseract by Gregorius van + den Hoven. + +1.8 2013/12 +=========== + * Fixed build of tommy_arrayblk in C++. + * Changed the default node size of tommy_trie to fit a cache line of 64 bytes. + * Added benchmark comparison with STX BTree. + +1.7 2013/12 +=========== + * Extends tommy_hashlin_done() to work also if the hashtable is not empty. + * Removes the empty tommy_trie_done() because the real deallocation is done + by the allocator. + +1.6 2013/11 +=========== + * Added a new tommy_arrayblk and tommy_arrayblkof types to store elements + in an array minimizing memory occupation. + +1.5 2013/06 +=========== + * Fixed inline declaration to allow building with clang. + * Added a new tommy_arrayof type to store in an array elements of arbitrary + size. + +1.4 2013/03 +=========== + * Added benchmark comparison with Google BTree, and C++ map and unordered_map. + * Benchmark for Linux is now compiled with "-O3 -march=pentium4 -mtune=generic", + and the Windows one with "/Ox /GL /GS- /arch:SSE2". + +1.3 2013/02 +=========== + * Fixed a Segmentation Fault bug in the hashlin container if exact power + of 2 sizes were used. + * Removed some warnings with newer gcc. + * Minor documentation changes. + * Added benchmark comparison with the judy array implementation by Karl Malbrain. + +1.2 2012/05 +=========== + * Minor documentation changes. + * In the check application, added a speed comparison with the C qsort() + implementation. + +1.1 2012/05 +=========== + * Fixed the tommy_hashdyn_remove() function. Now it shrinks the hashtable if required. + * Minor documentation changes. + +1.0 2011/03 +=========== + * First official version of TommyDS. + * Added tommy_list_foreach functions. + +0.2 2011/03 +=========== + * Added tommy_array. A dynamic vector. + +0.1 2011/01 +=========== + * First release of Tommy. + diff --git a/third-party/tommyds/INSTALL b/third-party/tommyds/INSTALL new file mode 100644 index 0000000..40e55ff --- /dev/null +++ b/third-party/tommyds/INSTALL @@ -0,0 +1,8 @@ +TommyDS INSTALL +=============== + +TommyDS doesn't need any installation. + +You have only to import the required .c and .h files into your program +and use the them. + diff --git a/third-party/tommyds/LICENSE b/third-party/tommyds/LICENSE new file mode 100644 index 0000000..6c86157 --- /dev/null +++ b/third-party/tommyds/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/tommyds/Makefile b/third-party/tommyds/Makefile new file mode 100644 index 0000000..a2a3d36 --- /dev/null +++ b/third-party/tommyds/Makefile @@ -0,0 +1,219 @@ +############################################################################# +# Tommy Makefile + +# Version of TommyDS +VERSION = 2.2 + +# Build options for the check program +ifdef COVERAGE +CFLAGS = -O0 -g -fprofile-arcs -ftest-coverage +else +CFLAGS = -O3 -march=native -Wall -Wextra -Wshadow -Wuninitialized -Wpadded -Wcast-align -Wcast-qual -g +endif + +# Build options for the benchmark +# -std=gnu++0x required by Google btree +BENCHCXXFLAGS = -m32 -O3 -march=native -flto -fpermissive -std=gnu++0x -Wall -g + +# Programs +CC ?= gcc +CXX ?= g++ +OBJDUMP ?= objdump +UNAME = $(shell uname) + +# Linux +ifeq ($(UNAME),Linux) +LIB=-lrt +BENCHLIB=benchmark/lib/judy/libJudyL.a benchmark/lib/judy/libJudyMalloc.a +EXE= +O=.o +endif + +# Darwin +ifeq ($(UNAME),Darwin) +LIB= +EXE= +O=.o +endif + +# Windows +ifeq ($(UNAME),) +BENCHLIB=benchmark/lib/judy/src/judy.lib +EXE=.exe +O=.obj +endif + +#CHECK = ./tommybench -n 1000000 -d tommy-hashlin +CHECK = ./tommycheck + +DEP = \ + tommyds/tommyalloc.c \ + tommyds/tommyalloc.h \ + tommyds/tommyarray.c \ + tommyds/tommyarray.h \ + tommyds/tommyarrayof.c \ + tommyds/tommyarrayof.h \ + tommyds/tommyarrayblk.c \ + tommyds/tommyarrayblk.h \ + tommyds/tommyarrayblkof.c \ + tommyds/tommyarrayblkof.h \ + tommyds/tommy.c \ + tommyds/tommy.h \ + tommyds/tommyhash.c \ + tommyds/tommyhashdyn.c \ + tommyds/tommyhashdyn.h \ + tommyds/tommyhash.h \ + tommyds/tommyhashlin.c \ + tommyds/tommyhashlin.h \ + tommyds/tommyhashtbl.c \ + tommyds/tommyhashtbl.h \ + tommyds/tommylist.c \ + tommyds/tommylist.h \ + tommyds/tommytrie.c \ + tommyds/tommytrie.h \ + tommyds/tommytrieinp.c \ + tommyds/tommytrieinp.h \ + tommyds/tommytypes.h \ + tommyds/tommychain.h + +DEPTEST = \ + check.c \ + benchmark.cc + +all: tommycheck$(EXE) + +bench: tommybench$(EXE) + +tommy$(O): $(DEP) + $(CC) $(CFLAGS) -c tommyds/tommy.c -o tommy$(O) + -$(OBJDUMP) -S tommy$(O) > tommy.s + +tommycheck$(EXE): check.c tommy$(O) + $(CC) $(CFLAGS) check.c tommy$(O) -o tommycheck$(EXE) $(LIB) + +tommybench$(EXE): benchmark.cc $(DEP) + $(CXX) $(BENCHCXXFLAGS) benchmark.cc -o tommybench$(EXE) $(LIB) $(BENCHLIB) + +check: tommycheck$(EXE) + ./tommycheck$(EXE) + echo Check completed with success! + +lcov_reset: + lcov -d . -z + rm -f ./lcov.info + +lcov_capture: + lcov -d . --capture -o lcov.info + +lcov_html: + rm -rf ./cov + mkdir cov + genhtml -o ./cov lcov.info + +coverage: + $(MAKE) COVERAGE=1 tommycheck$(EXE) + $(MAKE) lcov_reset + ./tommycheck$(EXE) + $(MAKE) lcov_capture + $(MAKE) lcov_html + +valgrind: + valgrind \ + --tool=memcheck \ + --track-origins=yes \ + --read-var-info=yes \ + -v $(CHECK) \ + 2> valgrind.log + tail valgrind.log + +callgrind: + valgrind \ + --tool=callgrind \ + --dump-instr=yes \ + --trace-jump=yes \ + -v $(CHECK) \ + 2> callgrind.log + tail callgrind.log + +cachegrind: + valgrind \ + --tool=cachegrind \ + -v $(CHECK) \ + 2> cachegrind.log + tail cachegrind.log + +phony: + +graph: phony + cd benchmark && sh gr_all.sh + +doc: phony tommy.doxygen tommy.css $(DEP) + rm -rf doc + mkdir doc + cp -a benchmark/data/def doc/def + cp -a benchmark/data/other doc/other + cp -a benchmark/data/core_i5_650_3G2_linux doc/core_i5_650_3G2_linux + rm -f doc/*/*.lst + rm -f doc/*/*.gnu + doxygen tommy.doxygen + rm -f doc/doxygen.png + rm -f doc/tab_*.png + +web: phony tommyweb.doxygen tommy.css $(DEP) + rm -rf web + mkdir web + cp -a benchmark/data/def web/def + cp -a benchmark/data/other web/other + cp -a benchmark/data/core_i5_650_3G2_linux web/core_i5_650_3G2_linux + rm -f web/*/*.lst + rm -f web/*/*.gnu + doxygen tommyweb.doxygen + rm -f web/doxygen.png + rm -f web/tab_*.png + +clean: + rm -f *.log *.s *.lst *.o + rm -f *.ncb *.suo *.obj + rm -f *.gcno *.gcda lcov.info + rm -rf Debug Release x64 + rm -f callgrind.out.* + rm -f cachegrind.out.* + +distclean: clean + rm -f tommybench$(EXE) tommycheck$(EXE) + +maintainerclean: distclean + rm -rf doc web + +DIST=tommyds-$(VERSION) + +DISTFILES=\ + Makefile \ + README LICENSE AUTHORS INSTALL HISTORY \ + tommy.doxygen tommy.css tommy-header.html tommy-footer.html \ + benchmark.vcxproj benchmark.sln \ + benchmark.geany \ + benchmark.cc \ + check.c + +dist: + mkdir $(DIST) + mkdir $(DIST)/tommyds + cp $(DISTFILES) $(DIST) + cp $(DEP) $(DIST)/tommyds + cp $(DEPTEST) $(DIST) + cp -R doc $(DIST) + cp -R benchmark $(DIST)/benchmark + rm -f $(DIST)/benchmark/data/*/*.png + rm -rf $(DIST)/benchmark/data/test + rm -f $(DIST)/benchmark/arial.ttf + rm -f $(DIST).tar.gz + tar cfzo $(DIST).tar.gz $(DIST) + rm -f $(DIST).zip + zip -r $(DIST).zip $(DIST) + rm -r $(DIST) + +distcheck: dist + tar zxvf $(DIST).tar.gz + cd $(DIST) && make check + rm -rf $(DIST) diff --git a/third-party/tommyds/README b/third-party/tommyds/README new file mode 100644 index 0000000..c0fa3ec --- /dev/null +++ b/third-party/tommyds/README @@ -0,0 +1,31 @@ +TommyDS +======= + +TommyDS is a C library of array, hashtables and tries data structures, +designed for high performance and providing an easy to use interface. + +It's faster than all the similar libraries like rbtree, judy, goodledensehash, +khash, uthash, nedtries and others. + +The data structures provided are: + + tommy_list - A double linked list. + tommy_array - A linear array. It doesn't fragment + the heap. + tommy_arrayblk - A blocked linear array. It doesn't fragment + the heap and it minimizes the space occupation. + tommy_hashtable - A fixed size chained hashtable. + tommy_hashdyn - A dynamic chained hashtable. + tommy_hashlin - A linear chained hashtable. It doesn't have the + problem of the delay when resizing and it doesn't + fragment the heap. + tommy_trie - A trie optimized for cache utilization. + tommy_trie_inplace - A trie completely inplace. + +The documentation is available in HTML format in the doc/index.html file, +and directly in the .h files. + +The official site of TommyDS is: + + http://www.tommyds.it + diff --git a/third-party/tommyds/tommy.c b/third-party/tommyds/tommy.c new file mode 100644 index 0000000..a05013a --- /dev/null +++ b/third-party/tommyds/tommy.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyhash.c" +#include "tommyalloc.c" +#include "tommyarray.c" +#include "tommyarrayof.c" +#include "tommyarrayblk.c" +#include "tommyarrayblkof.c" +#include "tommylist.c" +#include "tommytree.c" +#include "tommytrie.c" +#include "tommytrieinp.c" +#include "tommyhashtbl.c" +#include "tommyhashdyn.c" +#include "tommyhashlin.c" + diff --git a/third-party/tommyds/tommy.h b/third-party/tommyds/tommy.h new file mode 100644 index 0000000..08b0a75 --- /dev/null +++ b/third-party/tommyds/tommy.h @@ -0,0 +1,763 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \mainpage + * \section Introduction + * Tommy is a C library of array, hashtables and tries data structures, + * designed for high performance and providing an easy to use interface. + * + * It's <b>faster</b> than all the similar libraries like + * <a href="http://www.canonware.com/rb/">rbtree</a>, + * <a href="http://judy.sourceforge.net/">judy</a>, + * <a href="http://code.google.com/p/cpp-btree/">googlebtree</a>, + * <a href="http://panthema.net/2007/stx-btree/">stxbtree</a>, + * <a href="http://attractivechaos.awardspace.com/">khash</a>, + * <a href="http://uthash.sourceforge.net/">uthash</a>, + * <a href="http://www.nedprod.com/programs/portable/nedtries/">nedtrie</a>, + * <a href="http://code.google.com/p/judyarray/">judyarray</a>, + * <a href="http://concurrencykit.org/">concurrencykit</a> and others. + * Only <a href="http://code.google.com/p/google-sparsehash/">googledensehash</a> is a real competitor for Tommy. + * + * The data structures provided are: + * + * - ::tommy_list - A double linked list. + * - ::tommy_array, ::tommy_arrayof - A linear array. + * It doesn't fragment the heap. + * - ::tommy_arrayblk, ::tommy_arrayblkof - A blocked linear array. + * It doesn't fragment the heap and it minimizes the space occupation. + * - ::tommy_hashtable - A fixed size chained hashtable. + * - ::tommy_hashdyn - A dynamic chained hashtable. + * - ::tommy_hashlin - A linear chained hashtable. + * It doesn't have the problem of the delay when resizing and + * it doesn't fragment the heap. + * - ::tommy_trie - A trie optimized for cache utilization. + * - ::tommy_trie_inplace - A trie completely inplace. + * - ::tommy_tree - A tree to keep elements in order. + * + * The most interesting are ::tommy_array, ::tommy_hashdyn, ::tommy_hashlin, ::tommy_trie and ::tommy_trie_inplace. + * + * The official site of TommyDS is <a href="http://www.tommyds.it/">http://www.tommyds.it/</a>, + * + * \section Use + * + * All the Tommy containers are used to store pointers to generic objects, associated to an + * integer value, that could be a key or a hash value. + * + * They are semantically equivalent at the C++ <a href="http://www.cplusplus.com/reference/map/multimap/">multimap\<unsigned,void*\></a> + * and <a href="http://www.cplusplus.com/reference/unordered_map/unordered_multimap/">unordered_multimap\<unsigned,void*\></a>. + * + * An object, to be inserted in a container, should contain a node of type ::tommy_node. + * Inside this node is present a pointer to the object itself in the tommy_node::data field, + * the key used to identify the object in the tommy_node::key field, and other fields used + * by the containers. + * + * This is a typical object declaration: + * \code + * struct object { + * // other fields + * tommy_node node; + * }; + * \endcode + * + * To insert an object in a container, you have to provide the address of the embedded node, + * the address of the object and the value of the key. + * \code + * int key_to_insert = 1; + * struct object* obj = malloc(sizeof(struct object)); + * ... + * tommy_trie_insert(..., &obj->node, obj, key_to_insert); + * \endcode + * + * To search an object you have to provide the key and call the search function. + * \code + * int key_to_find = 1; + * struct object* obj = tommy_trie_search(..., key_to_find); + * if (obj) { + * // found + * } + * \endcode + * + * To access all the objects with the same keys you have to iterate over the bucket + * assigned at the specified key. + * \code + * int key_to_find = 1; + * tommy_trie_node* i = tommy_trie_bucket(..., key_to_find); + * + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * printf("%d\n", obj->value); // process the object + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an object you have to provide the key and call the remove function. + * \code + * int key_to_remove = 1; + * struct object* obj = tommy_trie_remove(..., key_to_remove); + * if (obj) { + * // found + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * Dealing with hashtables, instead of the key, you have to provide the hash + * value of the object, and a compare function able to differentiate objects with + * the same hash value. + * To compute the hash value, you can use the generic tommy_hash_u32() function, + * or the specialized integer hash function tommy_inthash_u32(). + * + * \section Features + * + * Tommy is fast and easy to use. + * + * Tommy is portable to all platforms and operating systems. + * + * Tommy containers support multiple elements with the same key. + * + * Tommy containers keep the original insertion order of elements with equal keys. + * + * Tommy is released with the \ref license "2-clause BSD license". + * + * See the \ref design page for more details and limitations. + * + * \section Performance + * Here you can see some timings comparing with other notable implementations. + * The <i>Hit</i> graph shows the time required for searching random objects + * with a key. + * The <i>Change</i> graph shows the time required for searching, removing and + * reinsert random objects with a different key value. + * + * Times are expressed in nanoseconds for each element, and <b>lower is better</b>. + * + * To have some reference numbers, you can check <a href="https://gist.github.com/jboner/2841832">Latency numbers every programmer should know</a>. + * + * A complete analysis is available in the \ref benchmark page. + * + * <img src="def/img_random_hit.png"/> + * + * <img src="def/img_random_change.png"/> + * + * \page benchmark Tommy Benchmarks + * + * To evaluate Tommy performances, an extensive benchmark was done, + * comparing it to the best libraries of data structures available: + * + * Specifically we test: + * - ::tommy_hashtable - Fixed size chained hashtable. + * - ::tommy_hashdyn - Dynamic chained hashtable. + * - ::tommy_hashlin - Linear chained hashtable. + * - ::tommy_trie - Trie optimized for cache usage. + * - ::tommy_trie_inplace - Trie completely inplace. + * - <a href="http://www.canonware.com/rb/">rbtree</a> - Red-black tree by Jason Evans. + * - <a href="http://www.nedprod.com/programs/portable/nedtries/">nedtrie</a> - Binary trie inplace by Niall Douglas. + * - <a href="http://attractivechaos.awardspace.com/">khash</a> - Dynamic open addressing hashtable by Attractive Chaos. + * - <a href="http://uthash.sourceforge.net/">uthash</a> - Dynamic chaining hashtable by Troy D. Hanson. + * - <a href="http://judy.sourceforge.net/">judy</a> - Burst trie (JudyL) by Doug Baskins. + * - <a href="http://code.google.com/p/judyarray/">judyarray</a> - Burst trie by Karl Malbrain. + * - <a href="http://code.google.com/p/google-sparsehash/">googledensehash</a> - Dynamic open addressing hashtable by Craig Silverstein at Google. + * - <a href="http://code.google.com/p/cpp-btree/">googlebtree</a> - Btree by Google. + * - <a href="http://panthema.net/2007/stx-btree/">stxbtree</a> - STX Btree by Timo Bingmann. + * - <a href="http://www.cplusplus.com/reference/unordered_map/unordered_map/">c++unordered_map</a> - C++ STL unordered_map<> template. + * - <a href="http://www.cplusplus.com/reference/map/map/">c++map</a> - C++ STL map<> template. + * - <a href="https://sites.google.com/site/binarysearchcube/">tesseract</a> - Binary Search Tesseract by Gregorius van den Hoven. + * - <a href="https://code.google.com/p/sparsehash/source/browse/trunk/experimental/libchash.c">googlelibchash</a> - LibCHash by Craig Silverstein at Google. + * - <a href="https://github.com/fredrikwidlund/libdynamic">libdynamic</a> - Hash set by Fredrik Widlund. + * - <a href="http://concurrencykit.org/">concurrencykit</a> - Non-blocking hash set by Samy Al Bahra. + * + * Note that <em>googlelibchash</em> and <em>concurrencykit</em> are not shown in the graphs + * because they present a lot of spikes. See the \ref notes the end. + * + * \section thebenchmark The Benchmark + * + * The benchmark consists in storing a set of N pointers to objects and + * searching them using integer keys. + * + * Compared to the case of mapping integers to integers, mapping pointers to + * objects means that the pointers are also dereferenced, to simulate the + * object access, resulting in additional cache misses. + * This gives an advantage to implementations that store information in the + * objects itself, as the additional cache misses are already implicit. + * + * The test done are: + * - <b>Insert</b> Insert all the objects starting with an empty container. + * - <b>Change</b> Find and remove one object and reinsert it with a different key, repeated for all the objects. + * - <b>Hit</b> Find with success all the objects and dereference them. + * - <b>Miss</b> Find with failure all the objects. + * - <b>Remove</b> Remove all the objects and dereference them. + * + * The <i>Change</i>, <i>Hit</i> and <i>Miss</i> tests operate always with N + * objects in the containers. + * The <i>Insert</i> test starts with an empty container, and the <i>Remove</i> + * test ends with an empty container. + * The objects are always dereferenced, as we are supposing to use them. This + * happens even in the remove case, as we are supposing to deallocate them. + * + * All the objects are preallocated in the heap, and the allocation and deallocation + * time is not included in the test. + * + * The objects contain an integer <i>value</i> field used for consistency checks, + * an unused <i>payload</i> field of 16 bytes, and any other data required by the + * data structure. + * + * The objects are identified and stored using integer and unique <i>keys</i>. + * The key domain used is <strong>dense</strong>, and it's defined by the set + * of N even numbers starting from 0x80000000 to 0x80000000+2*N. + * + * The use of even numbers allows to have missing keys inside the domain for + * the <i>Miss</i> and <i>Change</i> test. + * In such tests it's used the key domain defined by the set of N odd numbers + * starting from 0x80000000+1 to 0x80000000+2*N+1. + * Note that using additional keys at the corners of the domain would have given + * an unfair advantage to tries and trees as they implicitly keep track of the + * maximum and minimum key value inserted. + * + * The use of the 0x80000000 base, allow to test a key domain not necessarily + * starting at 0. Using a 0 base would have given an unfair advantage to some + * implementation handling it as a special case. + * + * For all the hashtables the keys are hashed using the tommy_inthash_u32() + * function that ensures an uniform distribution. This hash function is also + * reversible, meaning that no collision is going to be caused by hashing the + * keys. For tries and trees the keys are not hashed, and used directly. + * + * The tests are repeated using keys in <i>Random</i> mode and in <i>Forward</i> + * mode. In the forward mode the key values are used in order from the lowest + * to the highest. + * In the random mode the key values are used in a completely random order. + * In the <i>Change</i> test in forward mode, each object is reinserted using + * the previous key incremented by 1. In random mode each object is reinserted + * using a completely different and uncorrelated key. + * + * The forward order advantages tries and trees as they use the key directly + * and they have a cache advantage on using consecutive keys. + * The random order advantages hashtables, as the hash function already + * randomizes the key. Usually real uses case are in between, and the random + * one is the worst case. + * + * \section result Results + * + * The most significant tests depend on your data usage model, but if in doubt, + * you can look at <i>Random Hit</i> and <i>Random Change</i>. + * They represent the real world worst condition. + * + * <img src="def/img_random_hit.png"/> + * + * In the <i>Random Hit</i> graph you can see a vertical split at the 100.000 + * elements limit. Before this limit the cache of modern processor is able to + * contains most of the data, and it allows a very fast access with almost all + * data structures. + * After this limit, the number of cache misses is the dominant factor, and + * the curve depends mainly on the number of cache-miss required. + * + * rbtree and nedtrie grow as log2(N) as they have two branches on each node, + * ::tommy_trie_inplace grows as log4(N), ::tommy_trie as log8(N) and hashtables + * are almost constant and don't grow. + * For ::tommy_trie_inplace and ::tommy_trie you can change the grow curve + * configuring a different number of branches for node. + * + * <img src="def/img_random_change.png"/> + * + * The <i>Random Change</i> graph confirms the vertical split at the 100.000 + * elements limit. It also show that hashtables are almost unbeatable with a + * random access. + * + * \section random Random order + * Here you can see the whole <i>Random</i> test results in different platforms. + * + * In the <i>Random</i> test, hashtables are almost always winning, seconds are + * tries, and as last trees. + * + * The best choices are ::tommy_hashdyn, ::tommy_hashlin, and googledensehash, + * with ::tommy_hashlin having the advantage to be real-time friendly and not + * increasing the heap fragmentation. + * <table border="0"> + * <tr><td> + * <img src="core_i5_650_3G2_linux/img_random_insert.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_random_hit.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_random_miss.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_random_change.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_random_remove.png"/> + * </td></tr> + * </table> + * + * \section forward Forward order + * Here you can see the whole <i>Forward</i> test results in different platforms. + * + * In the <i>Forward</i> test, tries are the winners. Hashtables are competitive + * until the cache limit, then they lose against tries. Trees are the slowest. + * + * The best choices are ::tommy_trie and ::tommy_trie_inplace, where ::tommy_trie is + * a bit faster, and ::tommy_trie_inplace doesn't require a custom allocator. + * + * Note that also hashtables are faster in forward order than random. This may + * seem a bit surprising as the hash function randomizes the access even with + * consecutive keys. This happens because the objects are allocated in consecutive + * memory, and accessing them in order, improves the cache utilization, even if + * the hashed key is random. + * + * Note that you can make hashtables to reach tries performance tweaking the hash + * function to put near keys allocated nearby. + * This is possible if you know in advance the distribution of keys. + * For example, in the benchmark you could use something like: + * \code + * #define hash(v) tommy_inthash_u32(v & ~0xF) + (v & 0xF) + * \endcode + * and make keys that differ only by the lowest bits to have hashes with the same + * property, resulting in objects stored nearby, and improving cache utilization. + * + * <table border="0"> + * <tr><td> + * <img src="core_i5_650_3G2_linux/img_forward_insert.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_forward_hit.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_forward_miss.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_forward_change.png"/> + * </td></tr><tr><td> + * <img src="core_i5_650_3G2_linux/img_forward_remove.png"/> + * </td></tr> + * </table> + * + * \section size Size + * Here you can see the memory usage of the different data structures. + * <table border="0"> + * <tr><td> + * <img src="core_i5_650_3G2_linux/img_random_size.png"/> + * </td></tr> + * </table> + * + * \section code Code + * + * The compilers used in the benchmark are: + * - <b>gcc 4.9.2</b> in Linux with options: -O3 -march=nehalem + * - <b>Visual C 2012</b> in Windows with options: /Ox /Oy- /GL /GS- /arch:SSE2 + * + * The following is pseudo code of the benchmark used. In this case it's written + * for the C++ unordered_map. + * + * \code + * #define N 10000000 // Number of elements + * #define PAYLOAD 16 // Size of the object + * + * // Basic object inserted in the colletion + * struct obj { + * unsigned value; // Key used for searching + * char payload[PAYLOAD]; + * }; + * + * // Custom hash function to avoid to use the STL one + * class custom_hash { + * public: + * unsigned operator()(unsigned key) const { return tommy_inthash_u32(key); } + * }; + * + * // Map collection from "unsigned" to "pointer to object" + * typedef std::unordered_map<unsigned, obj*, custom_hash> bag_t; + * bag_t bag; + * + * // Preallocate objects + * obj* OBJ = new obj[N]; + * + * // Keys used for inserting and searching elements + * unsigned INSERT[N]; + * unsigned SEARCH[N]; + * + * // Initialize the keys + * for(i=0;i<N;++i) { + * INSERT[i] = 0x80000000 + i * 2; + * SEARCH[i] = 0x80000000 + i * 2; + * } + * + * // If random order is required, shuffle the keys with Fisher-Yates + * // The two key orders are not correlated + * if (test_random) { + * std::random_shuffle(INSERT, INSERT + N); + * std::random_shuffle(SEARCH, SEARCH + N); + * } + * \endcode + * + * \subsection insertion Insert benchmark + * \code + * for(i=0;i<N;++i) { + * // Setup the element to insert + * unsigned key = INSERT[i]; + * obj* element = &OBJ[i]; + * element->value = key; + * + * // Insert it + * bag[key] = element; + * } + * \endcode + * + * \subsection change Change benchmark + * \code + * for(i=0;i<N;++i) { + * // Search the element + * unsigned key = SEARCH[i]; + * bag_t::iterator j = bag.find(key); + * if (j == bag.end()) + * abort(); + * + * // Remove it + * obj* element = j->second; + * bag.erase(j); + * + * // Reinsert the element with a new key + * // Use +1 in the key to ensure that the new key is unique + * key = INSERT[i] + 1; + * element->value = key; + * bag[key] = element; + * } + * \endcode + * + * \subsection hit Hit benchmark + * \code + * for(i=0;i<N;++i) { + * // Search the element + * // Use a different key order than insertion + * // Use +1 in the key because we run after the "Change" test + * unsigned key = SEARCH[i] + 1; + * bag_t::const_iterator j = bag.find(key); + * if (j == bag.end()) + * abort(); + * + * // Ensure that it's the correct element. + * // This operation is like using the object after finding it, + * // and likely involves a cache-miss operation. + * obj* element = j->second; + * if (element->value != key) + * abort(); + * } + * \endcode + * + * \subsection miss Miss benchmark + * \code + * for(i=0;i<N;++i) { + * // Search the element + * // All the keys are now shifted by +1 by the "Change" test, and we'll find nothing + * unsigned key = SEARCH[i]; + * bag_t::const_iterator j = bag.find(key); + * if (j != bag.end()) + * abort(); + * } + * \endcode + * + * \subsection remove Remove benchmark + * \code + * for(i=0;i<N;++i) { + * // Search the element + * // Use +1 in the key because we run after the "Change" test + * unsigned key = SEARCH[i] + 1; + * bag_t::iterator j = bag.find(key); + * if (j == bag.end()) + * abort(); + * + * // Remove it + * bag.erase(j); + * + * // Ensure that it's the correct element. + * obj* element = j->second; + * if (element->value != key) + * abort(); + * } + * \endcode + * + * \section others Other benchmarks + * Here some links to other performance comparison: + * + * <a href="http://attractivechaos.wordpress.com/2008/08/28/comparison-of-hash-table-libraries/">Comparison of Hash Table Libraries</a> + * + * <a href="http://incise.org/hash-table-benchmarks.html">Hash Table Benchmarks</a> + * + * \section notes Notes + * + * Here some notes about the data structure tested not part of Tommy. + * + * \subsection googlelibchash Google C libchash + * It's the C implementation located in the <i>experimental/</i> directory of the googlesparsehash archive. + * It has very bad performances in the <i>Change</i> test for some N values. + * See this <a href="other/googlelibchash_problem.png">graph</a> with a lot of spikes. + * The C++ version doesn't suffer of this problem. + * + * \subsection googledensehash Google C++ densehash + * It doesn't release memory on deletion. + * To avoid an unfair advantage in the <i>Remove</i> test, we force a periodic + * resize calling resize(0) after any deallocation. + * The resize is executed when the load factor is lower than 20%. + * + * \subsection khash khash + * It doesn't release memory on deletion. This gives an unfair advantage on the <i>Remove</i> test. + * + * \subsection nedtrie nedtrie + * I've found a crash bug when inserting keys with the 0 value. + * The <a href="https://github.com/ned14/nedtries/commit/21039696f27db4ffac70a82f89dc5d00ae74b332">fix</a> of this issue is now in the nedtries github. + * We do not use the C++ implementation as it doesn't compile with gcc 4.4.3. + * + * \subsection judy Judy + * Sometimes it has bad performances in some specific platform + * and for some specific input data size. + * This makes difficult to predict the performance, as it is usually good until + * you get one of these cases. + * See for example this <a href="other/judy_problem.png">graph</a> with a big replicable spike at 50.000 elements. + * + * \subsection ck Concurrency Kit + * It has very bad performances in the <i>Change</i> test for some N values. + * See this <a href="other/ck_problem.png">graph</a> with a lot of spikes. + * + * \page multiindex Tommy Multi Indexing + * + * Tommy provides only partial iterator support with the "foreach" functions. + * If you need real iterators you have to insert all the objects also in a ::tommy_list, + * and use the list as iterator. + * + * This technique allows to keep track of the insertion order with the list, + * and provides more search possibilities using different data structures for + * different search keys. + * + * See the next example, for a objects inserted in a ::tommy_list, and in + * two ::tommy_hashdyn using different keys. + * + * \code + * struct object { + * // data fields + * int value_0; + * int value_1; + * + * // for containers + * tommy_node list_node; // node for the list + * tommy_node hash_node_0; // node for the first hash + * tommy_node hash_node_1; // node for the second hash + * }; + * + * // search function for value_1 + * int search_1(const void* arg, const void* obj) + * { + * return *(const int*)arg != ((const struct object*)obj)->value_1; + * } + * + * tommy_hashdyn hash_0; + * tommy_hashdyn hash_1; + * tommy_list list; + * + * // initializes the hash tables and the list + * tommy_hashdyn_init(&hash_0); + * tommy_hashdyn_init(&hash_1); + * tommy_list_init(&list); + * + * ... + * + * // creates an object and inserts it + * struct object* obj = malloc(sizeof(struct object)); + * obj->value_0 = ...; + * obj->value_1 = ...; + * // inserts in the first hash table + * tommy_hashdyn_insert(&hash_0, &obj->hash_node_0, obj, tommy_inthash_u32(obj->value_0)); + * // inserts in the second hash table + * tommy_hashdyn_insert(&hash_1, &obj->hash_node_1, obj, tommy_inthash_u32(obj->value_1)); + * // inserts in the list + * tommy_list_insert_tail(&list, &obj->list_node, obj); + * + * ... + * + * // searches an object by value_1 and deletes it + * int value_to_find = ...; + * struct object* obj = tommy_hashdyn_search(&hash_1, search_1, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (obj) { + * // if found removes all the references + * tommy_hashdyn_remove_existing(&hash_0, &obj->hash_node_0); + * tommy_hashdyn_remove_existing(&hash_1, &obj->hash_node_1); + * tommy_list_remove_existing(&list, &obj->list_node); + * } + * + * ... + * + * // complex iterator logic + * tommy_node* i = tommy_list_head(&list); + * while (i != 0) { + * // get the object + * struct object* obj = i->data; + * ... + * // go to the next element + * i = i->next; + * ... + * // go to the prev element + * i = i->prev; + * ... + * } + * + * ... + * + * // deallocates the objects iterating the list + * tommy_list_foreach(&list, free); + * + * // deallocates the hash tables + * tommy_hashdyn_done(&hash_0); + * tommy_hashdyn_done(&hash_1); + * \endcode + * + * \page design Tommy Design + * + * Tommy is designed to fulfill the need of generic data structures for the + * C language, providing at the same time high performance and a clean + * and easy to use interface. + * + * \section testing Testing + * + * Extensive and automated tests with the runtime checker <a href="http://valgrind.org/">valgrind</a> + * and the static analyzer <a href="http://clang-analyzer.llvm.org/">clang</a> + * are done to ensure the correctness of the library. + * + * The test has a <a href="http://www.tommyds.it/cov/tommyds/tommyds">code coverage of 100%</a>, + * measured with <a href="http://ltp.sourceforge.net/coverage/lcov.php">lcov</a>. + * + * \section Limitations + * + * Tommy is not thread safe. You have always to provide thread safety using + * locks before calling any Tommy functions. + * + * Tommy doesn't provide iterators over the implicit order defined by the data + * structures. To iterate on elements you must insert them also into a ::tommy_list, + * and use the list as iterator. See the \ref multiindex example for more details. + * Note that this is a real limitation only for ::tommy_trie, as it's the only + * data structure defining an useable order. + * + * Tommy doesn't provide an error reporting mechanism for a malloc() failure. + * You have to provide it redefining malloc() if you expect it to fail. + * + * Tommy assumes to never have more than 2^32-1 elements in a container. + * + * \section compromise Compromises + * + * Finding the right balance between efficency and easy to use required some + * comprimises. Most of them are on memory efficency, and were done to avoid to + * cripple the interface. + * + * The following is a list of such decisions. + * + * \subsection multi_key Multi key + * All the Tommy containers support the insertion of multiple elements with + * the same key, adding in each node a list of equal elements. + * + * They are the equivalent at the C++ associative containers <a href="http://www.cplusplus.com/reference/map/multimap/">multimap\<unsigned,void*\></a> + * and <a href="http://www.cplusplus.com/reference/unordered_map/unordered_multimap/">unordered_multimap\<unsigned,void*\></a> + * that allow duplicates of the same key. + * + * A more memory conservative approach is to not allow duplicated elements, + * removing the need of this list. + * + * \subsection data_pointer Data pointer + * The tommy_node::data field is present to allow search and remove functions to return + * directly a pointer to the element stored in the container. + * + * A more memory conservative approach is to require the user to compute + * the element pointer from the embedded node with a fixed displacement. + * For an example, see the Linux Kernel declaration of + * <a href="http://lxr.free-electrons.com/ident?i=container_of">container_of()</a>. + * + * \subsection insertion_order Insertion order + * The list used for collisions is double linked to allow + * insertion of elements at the end of the list to keep the + * insertion order of equal elements. + * + * A more memory conservative approach is to use a single linked list, + * inserting elements only at the start of the list, losing the + * original insertion order. + * + * \subsection zero_list Zero terminated list + * The 0 terminated format of tommy_node::next is present to provide a forward + * iterator terminating in 0. This allows the user to write a simple iteration + * loop over the list of elements in the same bucket. + * + * A more efficient approach is to use a circular list, because operating on nodes + * in a circular list doesn't requires to manage the special terminating case when + * adding or removing elements. + * + * \page license Tommy License + * Tommy is released with a <i>2-clause BSD license</i>. + * + * \code + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * \endcode + */ + +/** \file + * All in one include for Tommy. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tommytypes.h" +#include "tommyhash.h" +#include "tommyalloc.h" +#include "tommyarray.h" +#include "tommyarrayof.h" +#include "tommyarrayblk.h" +#include "tommyarrayblkof.h" +#include "tommylist.h" +#include "tommytree.h" +#include "tommytrie.h" +#include "tommytrieinp.h" +#include "tommyhashtbl.h" +#include "tommyhashdyn.h" +#include "tommyhashlin.h" + +#ifdef __cplusplus +} +#endif + diff --git a/third-party/tommyds/tommyalloc.c b/third-party/tommyds/tommyalloc.c new file mode 100644 index 0000000..0da604c --- /dev/null +++ b/third-party/tommyds/tommyalloc.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyalloc.h" + +/******************************************************************************/ +/* allocator */ + +/** + * Basic allocation segment. + * Smaller of a memory page, to allow also a little heap overread. + * The heap manager may put it in a single memory page. + */ +#define TOMMY_ALLOCATOR_BLOCK_SIZE (4096 - 64) + +void tommy_allocator_init(tommy_allocator* alloc, tommy_size_t block_size, tommy_size_t align_size) +{ + /* setup the minimal alignment */ + if (align_size < sizeof(void*)) + align_size = sizeof(void*); + + /* ensure that the block_size keeps the alignment */ + if (block_size % align_size != 0) + block_size += align_size - block_size % align_size; + + alloc->block_size = block_size; + alloc->align_size = align_size; + + alloc->count = 0; + alloc->free_block = 0; + alloc->used_segment = 0; +} + +/** + * Reset the allocator and free all. + */ +static void allocator_reset(tommy_allocator* alloc) +{ + tommy_allocator_entry* block = alloc->used_segment; + + while (block) { + tommy_allocator_entry* block_next = block->next; + tommy_free(block); + block = block_next; + } + + alloc->count = 0; + alloc->free_block = 0; + alloc->used_segment = 0; +} + +void tommy_allocator_done(tommy_allocator* alloc) +{ + allocator_reset(alloc); +} + +void* tommy_allocator_alloc(tommy_allocator* alloc) +{ + void* ptr; + + /* if no free block available */ + if (!alloc->free_block) { + tommy_uintptr_t off, mis; + tommy_size_t size; + char* data; + tommy_allocator_entry* segment; + + /* default allocation size */ + size = TOMMY_ALLOCATOR_BLOCK_SIZE; + + /* ensure that we can allocate at least one block */ + if (size < sizeof(tommy_allocator_entry) + alloc->align_size + alloc->block_size) + size = sizeof(tommy_allocator_entry) + alloc->align_size + alloc->block_size; + + data = tommy_cast(char*, tommy_malloc(size)); + segment = (tommy_allocator_entry*)data; + + /* put in the segment list */ + segment->next = alloc->used_segment; + alloc->used_segment = segment; + data += sizeof(tommy_allocator_entry); + + /* align if not aligned */ + off = (tommy_uintptr_t)data; + mis = off % alloc->align_size; + if (mis != 0) { + data += alloc->align_size - mis; + size -= alloc->align_size - mis; + } + + /* insert in free list */ + do { + tommy_allocator_entry* free_block = (tommy_allocator_entry*)data; + free_block->next = alloc->free_block; + alloc->free_block = free_block; + + data += alloc->block_size; + size -= alloc->block_size; + } while (size >= alloc->block_size); + } + + /* remove one from the free list */ + ptr = alloc->free_block; + alloc->free_block = alloc->free_block->next; + + ++alloc->count; + + return ptr; +} + +void tommy_allocator_free(tommy_allocator* alloc, void* ptr) +{ + tommy_allocator_entry* free_block = tommy_cast(tommy_allocator_entry*, ptr); + + /* put it in the free list */ + free_block->next = alloc->free_block; + alloc->free_block = free_block; + + --alloc->count; +} + +tommy_size_t tommy_allocator_memory_usage(tommy_allocator* alloc) +{ + return alloc->count * (tommy_size_t)alloc->block_size; +} + diff --git a/third-party/tommyds/tommyalloc.h b/third-party/tommyds/tommyalloc.h new file mode 100644 index 0000000..7a9afea --- /dev/null +++ b/third-party/tommyds/tommyalloc.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Allocator of fixed size blocks. + */ + +#ifndef __TOMMYALLOC_H +#define __TOMMYALLOC_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* allocator */ + +/** \internal + * Allocator entry. + */ +struct tommy_allocator_entry_struct { + struct tommy_allocator_entry_struct* next; /**< Pointer to the next entry. 0 for last. */ +}; +typedef struct tommy_allocator_entry_struct tommy_allocator_entry; + +/** + * Allocator of fixed size blocks. + */ +typedef struct tommy_allocator_struct { + struct tommy_allocator_entry_struct* free_block; /**< List of free blocks. */ + struct tommy_allocator_entry_struct* used_segment; /**< List of allocated segments. */ + tommy_size_t block_size; /**< Block size. */ + tommy_size_t align_size; /**< Alignment size. */ + tommy_count_t count; /**< Number of allocated elements. */ +} tommy_allocator; + +/** + * Initializes the allocator. + * \param alloc Allocator to initialize. + * \param block_size Size of the block to allocate. + * \param align_size Minimum alignment requirement. No less than sizeof(void*). + */ +void tommy_allocator_init(tommy_allocator* alloc, tommy_size_t block_size, tommy_size_t align_size); + +/** + * Deinitialize the allocator. + * It also releases all the allocated memory to the heap. + * \param alloc Allocator to deinitialize. + */ +void tommy_allocator_done(tommy_allocator* alloc); + +/** + * Allocates a block. + * \param alloc Allocator to use. + */ +void* tommy_allocator_alloc(tommy_allocator* alloc); + +/** + * Deallocates a block. + * You must use the same allocator used in the tommy_allocator_alloc() call. + * \param alloc Allocator to use. + * \param ptr Block to free. + */ +void tommy_allocator_free(tommy_allocator* alloc, void* ptr); + +/** + * Gets the size of allocated memory. + * \param alloc Allocator to use. + */ +tommy_size_t tommy_allocator_memory_usage(tommy_allocator* alloc); + +#endif + diff --git a/third-party/tommyds/tommyarray.c b/third-party/tommyds/tommyarray.c new file mode 100644 index 0000000..a8d946b --- /dev/null +++ b/third-party/tommyds/tommyarray.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyarray.h" + +/******************************************************************************/ +/* array */ + +void tommy_array_init(tommy_array* array) +{ + tommy_uint_t i; + + /* fixed initial size */ + array->bucket_bit = TOMMY_ARRAY_BIT; + array->bucket_max = 1 << array->bucket_bit; + array->bucket[0] = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*))); + for (i = 1; i < TOMMY_ARRAY_BIT; ++i) + array->bucket[i] = array->bucket[0]; + + array->count = 0; +} + +void tommy_array_done(tommy_array* array) +{ + tommy_uint_t i; + + tommy_free(array->bucket[0]); + for (i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) { + void** segment = array->bucket[i]; + tommy_free(&segment[((tommy_ptrdiff_t)1) << i]); + } +} + +void tommy_array_grow(tommy_array* array, tommy_count_t count) +{ + if (array->count >= count) + return; + array->count = count; + + while (count > array->bucket_max) { + void** segment; + + /* allocate one more segment */ + segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*))); + + /* store it adjusting the offset */ + /* cast to ptrdiff_t to ensure to get a negative value */ + array->bucket[array->bucket_bit] = &segment[-(tommy_ptrdiff_t)array->bucket_max]; + + ++array->bucket_bit; + array->bucket_max = 1 << array->bucket_bit; + } +} + +tommy_size_t tommy_array_memory_usage(tommy_array* array) +{ + return array->bucket_max * (tommy_size_t)sizeof(void*); +} + diff --git a/third-party/tommyds/tommyarray.h b/third-party/tommyds/tommyarray.h new file mode 100644 index 0000000..c04193f --- /dev/null +++ b/third-party/tommyds/tommyarray.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2011, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Dynamic array based on segments of exponential growing size. + * + * This array is able to grow dynamically upon request, without any reallocation. + * + * The grow operation involves an allocation of a new array segment, without reallocating + * the already used memory, and then not increasing the heap fragmentation. + * This also implies that the address of the stored elements never change. + * + * Allocated segments grow in size exponentially. + */ + +#ifndef __TOMMYARRAY_H +#define __TOMMYARRAY_H + +#include "tommytypes.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* array */ + +/** + * Initial and minimal size of the array expressed as a power of 2. + * The initial size is 2^TOMMY_ARRAY_BIT. + */ +#define TOMMY_ARRAY_BIT 6 + +/** \internal + * Max number of elements as a power of 2. + */ +#define TOMMY_ARRAY_BIT_MAX 32 + +/** + * Array container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_array_struct { + void** bucket[TOMMY_ARRAY_BIT_MAX]; /**< Dynamic array of buckets. */ + tommy_uint_t bucket_bit; /**< Bits used in the bit mask. */ + tommy_count_t bucket_max; /**< Number of buckets. */ + tommy_count_t count; /**< Number of initialized elements in the array. */ +} tommy_array; + +/** + * Initializes the array. + */ +void tommy_array_init(tommy_array* array); + +/** + * Deinitializes the array. + */ +void tommy_array_done(tommy_array* array); + +/** + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + */ +void tommy_array_grow(tommy_array* array, tommy_count_t size); + +/** + * Gets a reference of the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_array_grow(). + */ +tommy_inline void** tommy_array_ref(tommy_array* array, tommy_count_t pos) +{ + tommy_uint_t bsr; + + assert(pos < array->count); + + /* get the highest bit set, in case of all 0, return 0 */ + bsr = tommy_ilog2_u32(pos | 1); + + return &array->bucket[bsr][pos]; +} + +/** + * Sets the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_array_grow(). + */ +tommy_inline void tommy_array_set(tommy_array* array, tommy_count_t pos, void* element) +{ + *tommy_array_ref(array, pos) = element; +} + +/** + * Gets the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_array_grow(). + */ +tommy_inline void* tommy_array_get(tommy_array* array, tommy_count_t pos) +{ + return *tommy_array_ref(array, pos); +} + +/** + * Grows and inserts a new element at the end of the array. + */ +tommy_inline void tommy_array_insert(tommy_array* array, void* element) +{ + tommy_count_t pos = array->count; + + tommy_array_grow(array, pos + 1); + + tommy_array_set(array, pos, element); +} + +/** + * Gets the initialized size of the array. + */ +tommy_inline tommy_count_t tommy_array_size(tommy_array* array) +{ + return array->count; +} + +/** + * Gets the size of allocated memory. + */ +tommy_size_t tommy_array_memory_usage(tommy_array* array); + +#endif + diff --git a/third-party/tommyds/tommyarrayblk.c b/third-party/tommyds/tommyarrayblk.c new file mode 100644 index 0000000..d4dda76 --- /dev/null +++ b/third-party/tommyds/tommyarrayblk.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyarrayblk.h" + +/******************************************************************************/ +/* array */ + +void tommy_arrayblk_init(tommy_arrayblk* array) +{ + tommy_array_init(&array->block); + + array->count = 0; +} + +void tommy_arrayblk_done(tommy_arrayblk* array) +{ + tommy_count_t i; + + for (i = 0; i < tommy_array_size(&array->block); ++i) + tommy_free(tommy_array_get(&array->block, i)); + + tommy_array_done(&array->block); +} + +void tommy_arrayblk_grow(tommy_arrayblk* array, tommy_count_t count) +{ + tommy_count_t block_max; + tommy_count_t block_mac; + + if (array->count >= count) + return; + array->count = count; + + block_max = (count + TOMMY_ARRAYBLK_SIZE - 1) / TOMMY_ARRAYBLK_SIZE; + block_mac = tommy_array_size(&array->block); + + if (block_mac < block_max) { + /* grow the block array */ + tommy_array_grow(&array->block, block_max); + + /* allocate new blocks */ + while (block_mac < block_max) { + void** ptr = tommy_cast(void**, tommy_calloc(TOMMY_ARRAYBLK_SIZE, sizeof(void*))); + + /* set the new block */ + tommy_array_set(&array->block, block_mac, ptr); + + ++block_mac; + } + } +} + +tommy_size_t tommy_arrayblk_memory_usage(tommy_arrayblk* array) +{ + return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLK_SIZE * sizeof(void*); +} + diff --git a/third-party/tommyds/tommyarrayblk.h b/third-party/tommyds/tommyarrayblk.h new file mode 100644 index 0000000..20d0525 --- /dev/null +++ b/third-party/tommyds/tommyarrayblk.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Dynamic array based on blocks of fixed size. + * + * This array is able to grow dynamically upon request, without any reallocation. + * + * The grow operation involves an allocation of a new array block, without reallocating + * the already used memory, and then not increasing the heap fragmentation, + * and minimize the space occupation. + * This also implies that the address of the stored elements never change. + * + * Allocated blocks are always of the same fixed size of 4 Ki pointers. + */ + +#ifndef __TOMMYARRAYBLK_H +#define __TOMMYARRAYBLK_H + +#include "tommytypes.h" +#include "tommyarray.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* array */ + +/** + * Elements for each block. + */ +#define TOMMY_ARRAYBLK_SIZE (4 * 1024) + +/** + * Array container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_arrayblk_struct { + tommy_array block; /**< Array of blocks. */ + tommy_count_t count; /**< Number of initialized elements in the array. */ +} tommy_arrayblk; + +/** + * Initializes the array. + */ +void tommy_arrayblk_init(tommy_arrayblk* array); + +/** + * Deinitializes the array. + */ +void tommy_arrayblk_done(tommy_arrayblk* array); + +/** + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + */ +void tommy_arrayblk_grow(tommy_arrayblk* array, tommy_count_t size); + +/** + * Gets a reference of the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_arrayblk_grow(). + */ +tommy_inline void** tommy_arrayblk_ref(tommy_arrayblk* array, tommy_count_t pos) +{ + void** ptr; + + assert(pos < array->count); + + ptr = tommy_cast(void**, tommy_array_get(&array->block, pos / TOMMY_ARRAYBLK_SIZE)); + + return &ptr[pos % TOMMY_ARRAYBLK_SIZE]; +} + +/** + * Sets the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_arrayblk_grow(). + */ +tommy_inline void tommy_arrayblk_set(tommy_arrayblk* array, tommy_count_t pos, void* element) +{ + *tommy_arrayblk_ref(array, pos) = element; +} + +/** + * Gets the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_arrayblk_grow(). + */ +tommy_inline void* tommy_arrayblk_get(tommy_arrayblk* array, tommy_count_t pos) +{ + return *tommy_arrayblk_ref(array, pos); +} + +/** + * Grows and inserts a new element at the end of the array. + */ +tommy_inline void tommy_arrayblk_insert(tommy_arrayblk* array, void* element) +{ + tommy_count_t pos = array->count; + + tommy_arrayblk_grow(array, pos + 1); + + tommy_arrayblk_set(array, pos, element); +} + +/** + * Gets the initialized size of the array. + */ +tommy_inline tommy_count_t tommy_arrayblk_size(tommy_arrayblk* array) +{ + return array->count; +} + +/** + * Gets the size of allocated memory. + */ +tommy_size_t tommy_arrayblk_memory_usage(tommy_arrayblk* array); + +#endif + diff --git a/third-party/tommyds/tommyarrayblkof.c b/third-party/tommyds/tommyarrayblkof.c new file mode 100644 index 0000000..8a491a0 --- /dev/null +++ b/third-party/tommyds/tommyarrayblkof.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyarrayblkof.h" + +/******************************************************************************/ +/* array */ + +void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size) +{ + tommy_array_init(&array->block); + + array->element_size = element_size; + array->count = 0; +} + +void tommy_arrayblkof_done(tommy_arrayblkof* array) +{ + tommy_count_t i; + + for (i = 0; i < tommy_array_size(&array->block); ++i) + tommy_free(tommy_array_get(&array->block, i)); + + tommy_array_done(&array->block); +} + +void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_count_t count) +{ + tommy_count_t block_max; + tommy_count_t block_mac; + + if (array->count >= count) + return; + array->count = count; + + block_max = (count + TOMMY_ARRAYBLKOF_SIZE - 1) / TOMMY_ARRAYBLKOF_SIZE; + block_mac = tommy_array_size(&array->block); + + if (block_mac < block_max) { + /* grow the block array */ + tommy_array_grow(&array->block, block_max); + + /* allocate new blocks */ + while (block_mac < block_max) { + void** ptr = tommy_cast(void**, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size)); + + /* set the new block */ + tommy_array_set(&array->block, block_mac, ptr); + + ++block_mac; + } + } +} + +tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array) +{ + return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLKOF_SIZE * array->element_size; +} + diff --git a/third-party/tommyds/tommyarrayblkof.h b/third-party/tommyds/tommyarrayblkof.h new file mode 100644 index 0000000..fce6840 --- /dev/null +++ b/third-party/tommyds/tommyarrayblkof.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Dynamic array based on blocks of fixed size. + * + * This array is able to grow dynamically upon request, without any reallocation. + * + * This is very similar at ::tommy_arrayblk, but it allows to store elements of any + * size and not just pointers. + * + * Note that in this case tommy_arrayblkof_ref() returns a pointer to the element, + * that should be used for getting and setting elements in the array, + * as generic getter and setter are not available. + */ + +#ifndef __TOMMYARRAYBLKOF_H +#define __TOMMYARRAYBLKOF_H + +#include "tommytypes.h" +#include "tommyarray.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* array */ + +/** + * Elements for each block. + */ +#define TOMMY_ARRAYBLKOF_SIZE (4 * 1024) + +/** + * Array container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_arrayblkof_struct { + tommy_array block; /**< Array of blocks. */ + tommy_size_t element_size; /**< Size of the stored element in bytes. */ + tommy_count_t count; /**< Number of initialized elements in the array. */ +} tommy_arrayblkof; + +/** + * Initializes the array. + * \param element_size Size in byte of the element to store in the array. + */ +void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size); + +/** + * Deinitializes the array. + */ +void tommy_arrayblkof_done(tommy_arrayblkof* array); + +/** + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + */ +void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_count_t size); + +/** + * Gets a reference of the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_arrayblkof_grow(). + */ +tommy_inline void* tommy_arrayblkof_ref(tommy_arrayblkof* array, tommy_count_t pos) +{ + unsigned char* base; + + assert(pos < array->count); + + base = tommy_cast(unsigned char*, tommy_array_get(&array->block, pos / TOMMY_ARRAYBLKOF_SIZE)); + + return base + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size; +} + +/** + * Gets the initialized size of the array. + */ +tommy_inline tommy_count_t tommy_arrayblkof_size(tommy_arrayblkof* array) +{ + return array->count; +} + +/** + * Gets the size of allocated memory. + */ +tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array); + +#endif + diff --git a/third-party/tommyds/tommyarrayof.c b/third-party/tommyds/tommyarrayof.c new file mode 100644 index 0000000..80aede7 --- /dev/null +++ b/third-party/tommyds/tommyarrayof.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyarrayof.h" + +/******************************************************************************/ +/* array */ + +void tommy_arrayof_init(tommy_arrayof* array, tommy_size_t element_size) +{ + tommy_uint_t i; + + /* fixed initial size */ + array->element_size = element_size; + array->bucket_bit = TOMMY_ARRAYOF_BIT; + array->bucket_max = 1 << array->bucket_bit; + array->bucket[0] = tommy_calloc(array->bucket_max, array->element_size); + for (i = 1; i < TOMMY_ARRAYOF_BIT; ++i) + array->bucket[i] = array->bucket[0]; + + array->count = 0; +} + +void tommy_arrayof_done(tommy_arrayof* array) +{ + tommy_uint_t i; + + tommy_free(array->bucket[0]); + for (i = TOMMY_ARRAYOF_BIT; i < array->bucket_bit; ++i) { + unsigned char* segment = tommy_cast(unsigned char*, array->bucket[i]); + tommy_free(segment + (((tommy_ptrdiff_t)1) << i) * array->element_size); + } +} + +void tommy_arrayof_grow(tommy_arrayof* array, tommy_count_t count) +{ + if (array->count >= count) + return; + array->count = count; + + while (count > array->bucket_max) { + unsigned char* segment; + + /* allocate one more segment */ + segment = tommy_cast(unsigned char*, tommy_calloc(array->bucket_max, array->element_size)); + + /* store it adjusting the offset */ + /* cast to ptrdiff_t to ensure to get a negative value */ + array->bucket[array->bucket_bit] = segment - (tommy_ptrdiff_t)array->bucket_max * array->element_size; + + ++array->bucket_bit; + array->bucket_max = 1 << array->bucket_bit; + } +} + +tommy_size_t tommy_arrayof_memory_usage(tommy_arrayof* array) +{ + return array->bucket_max * (tommy_size_t)array->element_size; +} + diff --git a/third-party/tommyds/tommyarrayof.h b/third-party/tommyds/tommyarrayof.h new file mode 100644 index 0000000..8454838 --- /dev/null +++ b/third-party/tommyds/tommyarrayof.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2013, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Dynamic array based on segments of exponential growing size. + * + * This array is able to grow dynamically upon request, without any reallocation. + * + * This is very similar at ::tommy_array, but it allows to store elements of any + * size and not just pointers. + * + * Note that in this case tommy_arrayof_ref() returns a pointer to the element, + * that should be used for getting and setting elements in the array, + * as generic getter and setter are not available. + */ + +#ifndef __TOMMYARRAYOF_H +#define __TOMMYARRAYOF_H + +#include "tommytypes.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* array */ + +/** + * Initial and minimal size of the array expressed as a power of 2. + * The initial size is 2^TOMMY_ARRAYOF_BIT. + */ +#define TOMMY_ARRAYOF_BIT 6 + +/** \internal + * Max number of elements as a power of 2. + */ +#define TOMMY_ARRAYOF_BIT_MAX 32 + +/** + * Array container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_arrayof_struct { + void* bucket[TOMMY_ARRAYOF_BIT_MAX]; /**< Dynamic array of buckets. */ + tommy_size_t element_size; /**< Size of the stored element in bytes. */ + tommy_uint_t bucket_bit; /**< Bits used in the bit mask. */ + tommy_count_t bucket_max; /**< Number of buckets. */ + tommy_count_t count; /**< Number of initialized elements in the array. */ +} tommy_arrayof; + +/** + * Initializes the array. + * \param element_size Size in byte of the element to store in the array. + */ +void tommy_arrayof_init(tommy_arrayof* array, tommy_size_t element_size); + +/** + * Deinitializes the array. + */ +void tommy_arrayof_done(tommy_arrayof* array); + +/** + * Grows the size up to the specified value. + * All the new elements in the array are initialized with the 0 value. + */ +void tommy_arrayof_grow(tommy_arrayof* array, tommy_count_t size); + +/** + * Gets a reference of the element at the specified position. + * You must be sure that space for this position is already + * allocated calling tommy_arrayof_grow(). + */ +tommy_inline void* tommy_arrayof_ref(tommy_arrayof* array, tommy_count_t pos) +{ + unsigned char* ptr; + tommy_uint_t bsr; + + assert(pos < array->count); + + /* get the highest bit set, in case of all 0, return 0 */ + bsr = tommy_ilog2_u32(pos | 1); + + ptr = tommy_cast(unsigned char*, array->bucket[bsr]); + + return ptr + pos * array->element_size; +} + +/** + * Gets the initialized size of the array. + */ +tommy_inline tommy_count_t tommy_arrayof_size(tommy_arrayof* array) +{ + return array->count; +} + +/** + * Gets the size of allocated memory. + */ +tommy_size_t tommy_arrayof_memory_usage(tommy_arrayof* array); + +#endif + diff --git a/third-party/tommyds/tommychain.h b/third-party/tommyds/tommychain.h new file mode 100644 index 0000000..b15bc6a --- /dev/null +++ b/third-party/tommyds/tommychain.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Chain of nodes. + * A chain of nodes is an abstraction used to implements complex list operations + * like sorting. + * + * Do not use this directly. Use lists instead. + */ + +#ifndef __TOMMYCHAIN_H +#define __TOMMYCHAIN_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* chain */ + +/** + * Chain of nodes. + * A chain of nodes is a sequence of nodes with the following properties: + * - It contains at least one node. A chains of zero nodes cannot exist. + * - The next field of the tail is of *undefined* value. + * - The prev field of the head is of *undefined* value. + * - All the other inner prev and next fields are correctly set. + */ +typedef struct tommy_chain_struct { + tommy_node* head; /**< Pointer to the head of the chain. */ + tommy_node* tail; /**< Pointer to the tail of the chain. */ +} tommy_chain; + +/** + * Splices a chain in the middle of another chain. + */ +tommy_inline void tommy_chain_splice(tommy_node* first_before, tommy_node* first_after, tommy_node* second_head, tommy_node* second_tail) +{ + /* set the prev list */ + first_after->prev = second_tail; + second_head->prev = first_before; + + /* set the next list */ + first_before->next = second_head; + second_tail->next = first_after; +} + +/** + * Concats two chains. + */ +tommy_inline void tommy_chain_concat(tommy_node* first_tail, tommy_node* second_head) +{ + /* set the prev list */ + second_head->prev = first_tail; + + /* set the next list */ + first_tail->next = second_head; +} + +/** + * Merges two chains. + */ +tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp) +{ + tommy_node* first_i = first->head; + tommy_node* second_i = second->head; + + /* merge */ + while (1) { + if (cmp(first_i->data, second_i->data) > 0) { + tommy_node* next = second_i->next; + if (first_i == first->head) { + tommy_chain_concat(second_i, first_i); + first->head = second_i; + } else { + tommy_chain_splice(first_i->prev, first_i, second_i, second_i); + } + if (second_i == second->tail) + break; + second_i = next; + } else { + if (first_i == first->tail) { + tommy_chain_concat(first_i, second_i); + first->tail = second->tail; + break; + } + first_i = first_i->next; + } + } +} + +/** + * Merges two chains managing special degenerated cases. + * It's funtionally equivalent at tommy_chain_merge() but faster with already ordered chains. + */ +tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp) +{ + /* identify the condition first <= second */ + if (cmp(first->tail->data, second->head->data) <= 0) { + tommy_chain_concat(first->tail, second->head); + first->tail = second->tail; + return; + } + + /* identify the condition second < first */ + /* here we must be strict on comparison to keep the sort stable */ + if (cmp(second->tail->data, first->head->data) < 0) { + tommy_chain_concat(second->tail, first->head); + first->head = second->head; + return; + } + + tommy_chain_merge(first, second, cmp); +} + +/** + * Max number of elements as a power of 2. + */ +#define TOMMY_CHAIN_BIT_MAX 32 + +/** + * Sorts a chain. + * It's a stable merge sort using power of 2 buckets, with O(N*log(N)) complexity, + * similar at the one used in the SGI STL libraries and in the Linux Kernel, + * but faster on degenerated cases like already ordered lists. + * + * SGI STL stl_list.h + * http://www.sgi.com/tech/stl/stl_list.h + * + * Linux Kernel lib/list_sort.c + * http://lxr.linux.no/#linux+v2.6.36/lib/list_sort.c + */ +tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* cmp) +{ + /* + * Bit buckets of chains. + * Each bucket contains 2^i nodes or it's empty. + * The chain at address TOMMY_CHAIN_BIT_MAX is an independet variable operating as "carry". + * We keep it in the same "bit" vector to avoid reports from the valgrind tool sgcheck. + */ + tommy_chain bit[TOMMY_CHAIN_BIT_MAX + 1]; + + /** + * Value stored inside the bit bucket. + * It's used to know which bucket is empty of full. + */ + tommy_count_t counter; + tommy_node* node = chain->head; + tommy_node* tail = chain->tail; + tommy_count_t mask; + tommy_count_t i; + + counter = 0; + while (1) { + tommy_node* next; + tommy_chain* last; + + /* carry bit to add */ + last = &bit[TOMMY_CHAIN_BIT_MAX]; + bit[TOMMY_CHAIN_BIT_MAX].head = node; + bit[TOMMY_CHAIN_BIT_MAX].tail = node; + next = node->next; + + /* add the bit, propagating the carry */ + i = 0; + mask = counter; + while ((mask & 1) != 0) { + tommy_chain_merge_degenerated(&bit[i], last, cmp); + mask >>= 1; + last = &bit[i]; + ++i; + } + + /* copy the carry in the first empty bit */ + bit[i] = *last; + + /* add the carry in the counter */ + ++counter; + + if (node == tail) + break; + node = next; + } + + /* merge the buckets */ + i = tommy_ctz_u32(counter); + mask = counter >> i; + while (mask != 1) { + mask >>= 1; + if (mask & 1) + tommy_chain_merge_degenerated(&bit[i + 1], &bit[i], cmp); + else + bit[i + 1] = bit[i]; + ++i; + } + + *chain = bit[i]; +} + +#endif + diff --git a/third-party/tommyds/tommyhash.c b/third-party/tommyds/tommyhash.c new file mode 100644 index 0000000..cc7495d --- /dev/null +++ b/third-party/tommyds/tommyhash.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyhash.h" + +/******************************************************************************/ +/* hash */ + +tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr) +{ + /* allow unaligned read on Intel x86 and x86_64 platforms */ +#if defined(__i386__) || defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) || defined(_M_X64) + /* defines from http://predef.sourceforge.net/ */ + return *(const tommy_uint32_t*)ptr; +#else + const unsigned char* ptr8 = tommy_cast(const unsigned char*, ptr); + return ptr8[0] + ((tommy_uint32_t)ptr8[1] << 8) + ((tommy_uint32_t)ptr8[2] << 16) + ((tommy_uint32_t)ptr8[3] << 24); +#endif +} + +#define tommy_rot(x, k) \ + (((x) << (k)) | ((x) >> (32 - (k)))) + +#define tommy_mix(a, b, c) \ + do { \ + a -= c; a ^= tommy_rot(c, 4); c += b; \ + b -= a; b ^= tommy_rot(a, 6); a += c; \ + c -= b; c ^= tommy_rot(b, 8); b += a; \ + a -= c; a ^= tommy_rot(c, 16); c += b; \ + b -= a; b ^= tommy_rot(a, 19); a += c; \ + c -= b; c ^= tommy_rot(b, 4); b += a; \ + } while (0) + +#define tommy_final(a, b, c) \ + do { \ + c ^= b; c -= tommy_rot(b, 14); \ + a ^= c; a -= tommy_rot(c, 11); \ + b ^= a; b -= tommy_rot(a, 25); \ + c ^= b; c -= tommy_rot(b, 16); \ + a ^= c; a -= tommy_rot(c, 4); \ + b ^= a; b -= tommy_rot(a, 14); \ + c ^= b; c -= tommy_rot(b, 24); \ + } while (0) + +tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len) +{ + const unsigned char* key = tommy_cast(const unsigned char*, void_key); + tommy_uint32_t a, b, c; + + a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val; + + while (key_len > 12) { + a += tommy_le_uint32_read(key + 0); + b += tommy_le_uint32_read(key + 4); + c += tommy_le_uint32_read(key + 8); + + tommy_mix(a, b, c); + + key_len -= 12; + key += 12; + } + + switch (key_len) { + case 0 : + return c; /* used only when called with a zero length */ + case 12 : + c += tommy_le_uint32_read(key + 8); + b += tommy_le_uint32_read(key + 4); + a += tommy_le_uint32_read(key + 0); + break; + case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */ + case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */ + case 9 : c += key[8]; /* fallthrough */ + case 8 : + b += tommy_le_uint32_read(key + 4); + a += tommy_le_uint32_read(key + 0); + break; + case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */ + case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */ + case 5 : b += key[4]; /* fallthrough */ + case 4 : + a += tommy_le_uint32_read(key + 0); + break; + case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */ + case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */ + case 1 : a += key[0]; /* fallthrough */ + } + + tommy_final(a, b, c); + + return c; +} + +tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len) +{ + const unsigned char* key = tommy_cast(const unsigned char*, void_key); + tommy_uint32_t a, b, c; + + a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff); + c += init_val >> 32; + + while (key_len > 12) { + a += tommy_le_uint32_read(key + 0); + b += tommy_le_uint32_read(key + 4); + c += tommy_le_uint32_read(key + 8); + + tommy_mix(a, b, c); + + key_len -= 12; + key += 12; + } + + switch (key_len) { + case 0 : + return c + ((tommy_uint64_t)b << 32); /* used only when called with a zero length */ + case 12 : + c += tommy_le_uint32_read(key + 8); + b += tommy_le_uint32_read(key + 4); + a += tommy_le_uint32_read(key + 0); + break; + case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */ + case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */ + case 9 : c += key[8]; /* fallthrough */ + case 8 : + b += tommy_le_uint32_read(key + 4); + a += tommy_le_uint32_read(key + 0); + break; + case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */ + case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */ + case 5 : b += key[4]; /* fallthrough */ + case 4 : + a += tommy_le_uint32_read(key + 0); + break; + case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */ + case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */ + case 1 : a += key[0]; /* fallthrough */ + } + + tommy_final(a, b, c); + + return c + ((tommy_uint64_t)b << 32); +} + +tommy_uint32_t tommy_strhash_u32(tommy_uint64_t init_val, const void* void_key) +{ + const unsigned char* key = tommy_cast(const unsigned char*, void_key); + tommy_uint32_t a, b, c; + tommy_uint32_t m[3] = { 0xff, 0xff00, 0xff0000 }; + + a = b = c = 0xdeadbeef + init_val; + /* this is different than original lookup3 and the result won't match */ + + while (1) { + tommy_uint32_t v = tommy_le_uint32_read(key); + + if (tommy_haszero_u32(v)) { + if (v & m[0]) { + a += v & m[0]; + if (v & m[1]) { + a += v & m[1]; + if (v & m[2]) + a += v & m[2]; + } + } + + break; + } + + a += v; + + v = tommy_le_uint32_read(key + 4); + + if (tommy_haszero_u32(v)) { + if (v & m[0]) { + b += v & m[0]; + if (v & m[1]) { + b += v & m[1]; + if (v & m[2]) + b += v & m[2]; + } + } + + break; + } + + b += v; + + v = tommy_le_uint32_read(key + 8); + + if (tommy_haszero_u32(v)) { + if (v & m[0]) { + c += v & m[0]; + if (v & m[1]) { + c += v & m[1]; + if (v & m[2]) + c += v & m[2]; + } + } + + break; + } + + c += v; + + tommy_mix(a, b, c); + + key += 12; + } + + /* for lengths that are multiplers of 12 we already have called mix */ + /* this is different than the original lookup3 and the result won't match */ + + tommy_final(a, b, c); + + return c; +} + diff --git a/third-party/tommyds/tommyhash.h b/third-party/tommyds/tommyhash.h new file mode 100644 index 0000000..738e33d --- /dev/null +++ b/third-party/tommyds/tommyhash.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Hash functions for the use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin. + */ + +#ifndef __TOMMYHASH_H +#define __TOMMYHASH_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* hash */ + +/** + * Hash type used in hashtables. + */ +typedef tommy_key_t tommy_hash_t; + +/** + * Hash function with a 32 bits result. + * Implementation of the Robert Jenkins "lookup3" hash 32 bits version, + * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle(). + * + * This hash is designed to provide a good overall performance in all platforms, + * including 32 bits. If you target only 64 bits, you can use faster hashes, + * like SpookyHash or FarmHash. + * + * \param init_val Initialization value. + * Using a different initialization value, you can generate a completely different set of hash values. + * Use 0 if not relevant. + * \param void_key Pointer to the data to hash. + * \param key_len Size of the data to hash. + * \note + * This function is endianess independent. + * \return The hash value of 32 bits. + */ +tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len); + +/** + * Hash function with a 64 bits result. + * Implementation of the Robert Jenkins "lookup3" hash 64 bits versions, + * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle2(). + * + * This hash is designed to provide a good overall performance in all platforms, + * including 32 bits. If you target only 64 bits, you can use faster hashes, + * like SpookyHash or FarmHash. + * + * \param init_val Initialization value. + * Using a different initialization value, you can generate a completely different set of hash values. + * Use 0 if not relevant. + * \param void_key Pointer to the data to hash. + * \param key_len Size of the data to hash. + * \note + * This function is endianess independent. + * \return The hash value of 64 bits. + */ +tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len); + +/** + * String hash function with a 32 bits result. + * Implementation is based on the the Robert Jenkins "lookup3" hash 32 bits version, + * from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle(). + * + * This hash is designed to handle strings with an unknown length. If you + * know the string length, the other hash functions are surely faster. + * + * \param init_val Initialization value. + * Using a different initialization value, you can generate a completely different set of hash values. + * Use 0 if not relevant. + * \param void_key Pointer to the string to hash. It has to be 0 terminated. + * \note + * This function is endianess independent. + * \return The hash value of 32 bits. + */ +tommy_uint32_t tommy_strhash_u32(tommy_uint64_t init_val, const void* void_key); + +/** + * Integer reversible hash function for 32 bits. + * Implementation of the Robert Jenkins "4-byte Integer Hashing", + * from http://burtleburtle.net/bob/hash/integer.html + */ +tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key) +{ + key -= key << 6; + key ^= key >> 17; + key -= key << 9; + key ^= key << 4; + key -= key << 3; + key ^= key << 10; + key ^= key >> 15; + + return key; +} + +/** + * Integer reversible hash function for 64 bits. + * Implementation of the Thomas Wang "Integer Hash Function", + * from http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm + */ +tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key) +{ + key = ~key + (key << 21); + key = key ^ (key >> 24); + key = key + (key << 3) + (key << 8); + key = key ^ (key >> 14); + key = key + (key << 2) + (key << 4); + key = key ^ (key >> 28); + key = key + (key << 31); + + return key; +} + +#endif + diff --git a/third-party/tommyds/tommyhashdyn.c b/third-party/tommyds/tommyhashdyn.c new file mode 100644 index 0000000..02ef404 --- /dev/null +++ b/third-party/tommyds/tommyhashdyn.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyhashdyn.h" +#include "tommylist.h" + +/******************************************************************************/ +/* hashdyn */ + +void tommy_hashdyn_init(tommy_hashdyn* hashdyn) +{ + /* fixed initial size */ + hashdyn->bucket_bit = TOMMY_HASHDYN_BIT; + hashdyn->bucket_max = 1 << hashdyn->bucket_bit; + hashdyn->bucket_mask = hashdyn->bucket_max - 1; + hashdyn->bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(hashdyn->bucket_max, sizeof(tommy_hashdyn_node*))); + + hashdyn->count = 0; +} + +void tommy_hashdyn_done(tommy_hashdyn* hashdyn) +{ + tommy_free(hashdyn->bucket); +} + +/** + * Resize the bucket vector. + */ +static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_count_t new_bucket_bit) +{ + tommy_count_t bucket_bit; + tommy_count_t bucket_max; + tommy_count_t new_bucket_max; + tommy_count_t new_bucket_mask; + tommy_hashdyn_node** new_bucket; + + bucket_bit = hashdyn->bucket_bit; + bucket_max = hashdyn->bucket_max; + + new_bucket_max = 1 << new_bucket_bit; + new_bucket_mask = new_bucket_max - 1; + + /* allocate the new vector using malloc() and not calloc() */ + /* because data is fully initialized in the update process */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); + + /* reinsert all the elements */ + if (new_bucket_bit > bucket_bit) { + tommy_count_t i; + + /* grow */ + for (i = 0; i < bucket_max; ++i) { + tommy_hashdyn_node* j; + + /* setup the new two buckets */ + new_bucket[i] = 0; + new_bucket[i + bucket_max] = 0; + + /* reinsert the bucket */ + j = hashdyn->bucket[i]; + while (j) { + tommy_hashdyn_node* j_next = j->next; + tommy_count_t pos = j->key & new_bucket_mask; + if (new_bucket[pos]) + tommy_list_insert_tail_not_empty(new_bucket[pos], j); + else + tommy_list_insert_first(&new_bucket[pos], j); + j = j_next; + } + } + } else { + tommy_count_t i; + + /* shrink */ + for (i = 0; i < new_bucket_max; ++i) { + /* setup the new bucket with the lower bucket*/ + new_bucket[i] = hashdyn->bucket[i]; + + /* concat the upper bucket */ + tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]); + } + } + + tommy_free(hashdyn->bucket); + + /* setup */ + hashdyn->bucket_bit = new_bucket_bit; + hashdyn->bucket_max = new_bucket_max; + hashdyn->bucket_mask = new_bucket_mask; + hashdyn->bucket = new_bucket; +} + +/** + * Grow. + */ +tommy_inline void hashdyn_grow_step(tommy_hashdyn* hashdyn) +{ + /* grow if more than 50% full */ + if (hashdyn->count >= hashdyn->bucket_max / 2) + tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1); +} + +/** + * Shrink. + */ +tommy_inline void hashdyn_shrink_step(tommy_hashdyn* hashdyn) +{ + /* shrink if less than 12.5% full */ + if (hashdyn->count <= hashdyn->bucket_max / 8 && hashdyn->bucket_bit > TOMMY_HASHDYN_BIT) + tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1); +} + +void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash) +{ + tommy_count_t pos = hash & hashdyn->bucket_mask; + + tommy_list_insert_tail(&hashdyn->bucket[pos], node, data); + + node->key = hash; + + ++hashdyn->count; + + hashdyn_grow_step(hashdyn); +} + +void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node) +{ + tommy_count_t pos = node->key & hashdyn->bucket_mask; + + tommy_list_remove_existing(&hashdyn->bucket[pos], node); + + --hashdyn->count; + + hashdyn_shrink_step(hashdyn); + + return node->data; +} + +void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_count_t pos = hash & hashdyn->bucket_mask; + tommy_hashdyn_node* node = hashdyn->bucket[pos]; + + while (node) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (node->key == hash && cmp(cmp_arg, node->data) == 0) { + tommy_list_remove_existing(&hashdyn->bucket[pos], node); + + --hashdyn->count; + + hashdyn_shrink_step(hashdyn); + + return node->data; + } + node = node->next; + } + + return 0; +} + +void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func) +{ + tommy_count_t bucket_max = hashdyn->bucket_max; + tommy_hashdyn_node** bucket = hashdyn->bucket; + tommy_count_t pos; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashdyn_node* node = bucket[pos]; + + while (node) { + void* data = node->data; + node = node->next; + func(data); + } + } +} + +void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg) +{ + tommy_count_t bucket_max = hashdyn->bucket_max; + tommy_hashdyn_node** bucket = hashdyn->bucket; + tommy_count_t pos; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashdyn_node* node = bucket[pos]; + + while (node) { + void* data = node->data; + node = node->next; + func(arg, data); + } + } +} + +tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn) +{ + return hashdyn->bucket_max * (tommy_size_t)sizeof(hashdyn->bucket[0]) + + tommy_hashdyn_count(hashdyn) * (tommy_size_t)sizeof(tommy_hashdyn_node); +} + diff --git a/third-party/tommyds/tommyhashdyn.h b/third-party/tommyds/tommyhashdyn.h new file mode 100644 index 0000000..ed4a607 --- /dev/null +++ b/third-party/tommyds/tommyhashdyn.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Dynamic chained hashtable. + * + * This hashtable resizes dynamically. It starts with the minimal size of 16 buckets, it doubles + * the size then it reaches a load factor greater than 0.5 and it halves the size with a load + * factor lower than 0.125. + * + * All the elements are reallocated in a single resize operation done inside + * tommy_hashdyn_insert() or tommy_hashdyn_remove(). + * + * Note that the resize operation takes approximatively 100 [ms] with 1 million of elements, + * and 1 [second] with 10 millions. This could be a problem in real-time applications. + * + * The resize also fragment the heap, as it involves allocating a double-sized table, copy elements, + * and deallocating the older table. Leaving a big hole in the heap. + * + * The ::tommy_hashlin hashtable fixes both problems. + * + * To initialize the hashtable you have to call tommy_hashdyn_init(). + * + * \code + * tommy_hashslin hashdyn; + * + * tommy_hashdyn_init(&hashdyn); + * \endcode + * + * To insert elements in the hashtable you have to call tommy_hashdyn_insert() for + * each element. + * In the insertion call you have to specify the address of the node, the + * address of the object, and the hash value of the key to use. + * The address of the object is used to initialize the tommy_node::data field + * of the node, and the hash to initialize the tommy_node::key field. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object + * \endcode + * + * To find and element in the hashtable you have to call tommy_hashtable_search() + * providing a comparison function, its argument, and the hash of the key to search. + * + * \code + * int compare(const void* arg, const void* obj) + * { + * return *(const int*)arg != ((const struct object*)obj)->value; + * } + * + * int value_to_find = 1; + * struct object* obj = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To iterate over all the elements in the hashtable with the same key, you have to + * use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until NULL. + * You have also to check explicitely for the key, as the bucket may contains + * different keys. + * + * \code + * int value_to_find = 1; + * tommy_node* i = tommy_hashdyn_bucket(&hashdyn, tommy_inthash_u32(value_to_find)); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * if (obj->value == value_to_find) { + * printf("%d\n", obj->value); // process the object + * } + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an element from the hashtable you have to call tommy_hashdyn_remove() + * providing a comparison function, its argument, and the hash of the key to search + * and remove. + * + * \code + * struct object* obj = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove)); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the hashtable you have to remove all the elements, and deinitialize + * the hashtable calling tommy_hashdyn_done(). + * + * \code + * tommy_hashdyn_done(&hashdyn); + * \endcode + * + * If you need to iterate over all the elements in the hashtable, you can use + * tommy_hashdyn_foreach() or tommy_hashdyn_foreach_arg(). + * If you need a more precise control with a real iteration, you have to insert + * all the elements also in a ::tommy_list, and use the list to iterate. + * See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYHASHDYN_H +#define __TOMMYHASHDYN_H + +#include "tommyhash.h" + +/******************************************************************************/ +/* hashdyn */ + +/** \internal + * Initial and minimal size of the hashtable expressed as a power of 2. + * The initial size is 2^TOMMY_HASHDYN_BIT. + */ +#define TOMMY_HASHDYN_BIT 4 + +/** + * Hashtable node. + * This is the node that you have to include inside your objects. + */ +typedef tommy_node tommy_hashdyn_node; + +/** + * Hashtable container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_hashdyn_struct { + tommy_hashdyn_node** bucket; /**< Hash buckets. One list for each hash modulus. */ + tommy_uint_t bucket_bit; /**< Bits used in the bit mask. */ + tommy_count_t bucket_max; /**< Number of buckets. */ + tommy_count_t bucket_mask; /**< Bit mask to access the buckets. */ + tommy_count_t count; /**< Number of elements. */ +} tommy_hashdyn; + +/** + * Initializes the hashtable. + */ +void tommy_hashdyn_init(tommy_hashdyn* hashdyn); + +/** + * Deinitializes the hashtable. + * + * You can call this function with elements still contained, + * but such elements are not going to be freed by this call. + */ +void tommy_hashdyn_done(tommy_hashdyn* hashdyn); + +/** + * Inserts an element in the hashtable. + */ +void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash); + +/** + * Searches and removes an element from the hashtable. + * You have to provide a compare function and the hash of the element you want to remove. + * If the element is not found, 0 is returned. + * If more equal elements are present, the first one is removed. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find and remove. + * \return The removed element, or 0 if not found. + */ +void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash); + +/** + * Gets the bucket of the specified hash. + * The bucket is guaranteed to contain ALL the elements with the specified hash, + * but it can contain also others. + * You can access elements in the bucket following the ::next pointer until 0. + * \param hash Hash of the element to find. + * \return The head of the bucket, or 0 if empty. + */ +tommy_inline tommy_hashdyn_node* tommy_hashdyn_bucket(tommy_hashdyn* hashdyn, tommy_hash_t hash) +{ + return hashdyn->bucket[hash & hashdyn->bucket_mask]; +} + +/** + * Searches an element in the hashtable. + * You have to provide a compare function and the hash of the element you want to find. + * If more equal elements are present, the first one is returned. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashdyn_node* i = tommy_hashdyn_bucket(hashdyn, hash); + + while (i) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (i->key == hash && cmp(cmp_arg, i->data) == 0) + return i->data; + i = i->next; + } + return 0; +} + +/** + * Removes an element from the hashtable. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node); + +/** + * Calls the specified function for each element in the hashtable. + * + * You cannot add or remove elements from the inside of the callback, + * but can use it to deallocate them. + * + * \code + * tommy_hashdyn hashdyn; + * + * // initializes the hashtable + * tommy_hashdyn_init(&hashdyn); + * + * ... + * + * // creates an object + * struct object* obj = malloc(sizeof(struct object)); + * + * ... + * + * // insert it in the hashtable + * tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); + * + * ... + * + * // deallocates all the objects iterating the hashtable + * tommy_hashdyn_foreach(&hashdyn, free); + * + * // deallocates the hashtable + * tommy_hashdyn_done(&hashdyn); + * \endcode + */ +void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the hashtable. + */ +void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_hashdyn_count(tommy_hashdyn* hashdyn) +{ + return hashdyn->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_hashdyn_node of the stored elements. + */ +tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn); + +#endif + diff --git a/third-party/tommyds/tommyhashlin.c b/third-party/tommyds/tommyhashlin.c new file mode 100644 index 0000000..bcf7d98 --- /dev/null +++ b/third-party/tommyds/tommyhashlin.c @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyhashlin.h" +#include "tommylist.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* hashlin */ + +/** + * Reallocation states. + */ +#define TOMMY_HASHLIN_STATE_STABLE 0 +#define TOMMY_HASHLIN_STATE_GROW 1 +#define TOMMY_HASHLIN_STATE_SHRINK 2 + +/** + * Set the hashtable in stable state. + */ +tommy_inline void tommy_hashlin_stable(tommy_hashlin* hashlin) +{ + hashlin->state = TOMMY_HASHLIN_STATE_STABLE; + + /* setup low_mask/max/split to allow tommy_hashlin_bucket_ref() */ + /* and tommy_hashlin_foreach() to work regardless we are in stable state */ + hashlin->low_max = hashlin->bucket_max; + hashlin->low_mask = hashlin->bucket_mask; + hashlin->split = 0; +} + +void tommy_hashlin_init(tommy_hashlin* hashlin) +{ + tommy_uint_t i; + + /* fixed initial size */ + hashlin->bucket_bit = TOMMY_HASHLIN_BIT; + hashlin->bucket_max = 1 << hashlin->bucket_bit; + hashlin->bucket_mask = hashlin->bucket_max - 1; + hashlin->bucket[0] = tommy_cast(tommy_hashlin_node**, tommy_calloc(hashlin->bucket_max, sizeof(tommy_hashlin_node*))); + for (i = 1; i < TOMMY_HASHLIN_BIT; ++i) + hashlin->bucket[i] = hashlin->bucket[0]; + + /* stable state */ + tommy_hashlin_stable(hashlin); + + hashlin->count = 0; +} + +void tommy_hashlin_done(tommy_hashlin* hashlin) +{ + tommy_uint_t i; + + tommy_free(hashlin->bucket[0]); + for (i = TOMMY_HASHLIN_BIT; i < hashlin->bucket_bit; ++i) { + tommy_hashlin_node** segment = hashlin->bucket[i]; + tommy_free(&segment[((tommy_ptrdiff_t)1) << i]); + } +} + +/** + * Grow one step. + */ +tommy_inline void hashlin_grow_step(tommy_hashlin* hashlin) +{ + /* grow if more than 50% full */ + if (hashlin->state != TOMMY_HASHLIN_STATE_GROW + && hashlin->count > hashlin->bucket_max / 2 + ) { + /* if we are stable, setup a new grow state */ + /* otherwise continue with the already setup shrink one */ + /* but in backward direction */ + if (hashlin->state == TOMMY_HASHLIN_STATE_STABLE) { + tommy_hashlin_node** segment; + + /* set the lower size */ + hashlin->low_max = hashlin->bucket_max; + hashlin->low_mask = hashlin->bucket_mask; + + /* allocate the new vector using malloc() and not calloc() */ + /* because data is fully initialized in the split process */ + segment = tommy_cast(tommy_hashlin_node**, tommy_malloc(hashlin->low_max * sizeof(tommy_hashlin_node*))); + + /* store it adjusting the offset */ + /* cast to ptrdiff_t to ensure to get a negative value */ + hashlin->bucket[hashlin->bucket_bit] = &segment[-(tommy_ptrdiff_t)hashlin->low_max]; + + /* grow the hash size */ + ++hashlin->bucket_bit; + hashlin->bucket_max = 1 << hashlin->bucket_bit; + hashlin->bucket_mask = hashlin->bucket_max - 1; + + /* start from the beginning going forward */ + hashlin->split = 0; + } + + /* grow state */ + hashlin->state = TOMMY_HASHLIN_STATE_GROW; + } + + /* if we are growing */ + if (hashlin->state == TOMMY_HASHLIN_STATE_GROW) { + /* compute the split target required to finish the reallocation before the next resize */ + tommy_count_t split_target = 2 * hashlin->count; + + /* reallocate buckets until the split target */ + while (hashlin->split + hashlin->low_max < split_target) { + tommy_hashlin_node** split[2]; + tommy_hashlin_node* j; + tommy_count_t mask; + + /* get the low bucket */ + split[0] = tommy_hashlin_pos(hashlin, hashlin->split); + + /* get the high bucket */ + split[1] = tommy_hashlin_pos(hashlin, hashlin->split + hashlin->low_max); + + /* save the low bucket */ + j = *split[0]; + + /* reinitialize the buckets */ + *split[0] = 0; + *split[1] = 0; + + /* the bit used to identify the bucket */ + mask = hashlin->low_max; + + /* flush the bucket */ + while (j) { + tommy_hashlin_node* j_next = j->next; + tommy_count_t pos = (j->key & mask) != 0; + if (*split[pos]) + tommy_list_insert_tail_not_empty(*split[pos], j); + else + tommy_list_insert_first(split[pos], j); + j = j_next; + } + + /* go forward */ + ++hashlin->split; + + /* if we have finished, change the state */ + if (hashlin->split == hashlin->low_max) { + /* go in stable mode */ + tommy_hashlin_stable(hashlin); + break; + } + } + } +} + +/** + * Shrink one step. + */ +tommy_inline void hashlin_shrink_step(tommy_hashlin* hashlin) +{ + /* shrink if less than 12.5% full */ + if (hashlin->state != TOMMY_HASHLIN_STATE_SHRINK + && hashlin->count < hashlin->bucket_max / 8 + ) { + /* avoid to shrink the first bucket */ + if (hashlin->bucket_bit > TOMMY_HASHLIN_BIT) { + /* if we are stable, setup a new shrink state */ + /* otherwise continue with the already setup grow one */ + /* but in backward direction */ + if (hashlin->state == TOMMY_HASHLIN_STATE_STABLE) { + /* set the lower size */ + hashlin->low_max = hashlin->bucket_max / 2; + hashlin->low_mask = hashlin->bucket_mask / 2; + + /* start from the half going backward */ + hashlin->split = hashlin->low_max; + } + + /* start reallocation */ + hashlin->state = TOMMY_HASHLIN_STATE_SHRINK; + } + } + + /* if we are shrinking */ + if (hashlin->state == TOMMY_HASHLIN_STATE_SHRINK) { + /* compute the split target required to finish the reallocation before the next resize */ + tommy_count_t split_target = 8 * hashlin->count; + + /* reallocate buckets until the split target */ + while (hashlin->split + hashlin->low_max > split_target) { + tommy_hashlin_node** split[2]; + + /* go backward position */ + --hashlin->split; + + /* get the low bucket */ + split[0] = tommy_hashlin_pos(hashlin, hashlin->split); + + /* get the high bucket */ + split[1] = tommy_hashlin_pos(hashlin, hashlin->split + hashlin->low_max); + + /* concat the high bucket into the low one */ + tommy_list_concat(split[0], split[1]); + + /* if we have finished, clean up and change the state */ + if (hashlin->split == 0) { + tommy_hashlin_node** segment; + + /* shrink the hash size */ + --hashlin->bucket_bit; + hashlin->bucket_max = 1 << hashlin->bucket_bit; + hashlin->bucket_mask = hashlin->bucket_max - 1; + + /* free the last segment */ + segment = hashlin->bucket[hashlin->bucket_bit]; + tommy_free(&segment[((tommy_ptrdiff_t)1) << hashlin->bucket_bit]); + + /* go in stable mode */ + tommy_hashlin_stable(hashlin); + break; + } + } + } +} + +void tommy_hashlin_insert(tommy_hashlin* hashlin, tommy_hashlin_node* node, void* data, tommy_hash_t hash) +{ + tommy_list_insert_tail(tommy_hashlin_bucket_ref(hashlin, hash), node, data); + + node->key = hash; + + ++hashlin->count; + + hashlin_grow_step(hashlin); +} + +void* tommy_hashlin_remove_existing(tommy_hashlin* hashlin, tommy_hashlin_node* node) +{ + tommy_list_remove_existing(tommy_hashlin_bucket_ref(hashlin, node->key), node); + + --hashlin->count; + + hashlin_shrink_step(hashlin); + + return node->data; +} + +void* tommy_hashlin_remove(tommy_hashlin* hashlin, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashlin_node** let_ptr = tommy_hashlin_bucket_ref(hashlin, hash); + tommy_hashlin_node* node = *let_ptr; + + while (node) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (node->key == hash && cmp(cmp_arg, node->data) == 0) { + tommy_list_remove_existing(let_ptr, node); + + --hashlin->count; + + hashlin_shrink_step(hashlin); + + return node->data; + } + node = node->next; + } + + return 0; +} + +void tommy_hashlin_foreach(tommy_hashlin* hashlin, tommy_foreach_func* func) +{ + tommy_count_t bucket_max; + tommy_count_t pos; + + /* number of valid buckets */ + bucket_max = hashlin->low_max + hashlin->split; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashlin_node* node = *tommy_hashlin_pos(hashlin, pos); + + while (node) { + void* data = node->data; + node = node->next; + func(data); + } + } +} + +void tommy_hashlin_foreach_arg(tommy_hashlin* hashlin, tommy_foreach_arg_func* func, void* arg) +{ + tommy_count_t bucket_max; + tommy_count_t pos; + + /* number of valid buckets */ + bucket_max = hashlin->low_max + hashlin->split; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashlin_node* node = *tommy_hashlin_pos(hashlin, pos); + + while (node) { + void* data = node->data; + node = node->next; + func(arg, data); + } + } +} + +tommy_size_t tommy_hashlin_memory_usage(tommy_hashlin* hashlin) +{ + return hashlin->bucket_max * (tommy_size_t)sizeof(hashlin->bucket[0][0]) + + hashlin->count * (tommy_size_t)sizeof(tommy_hashlin_node); +} + diff --git a/third-party/tommyds/tommyhashlin.h b/third-party/tommyds/tommyhashlin.h new file mode 100644 index 0000000..eaf59e6 --- /dev/null +++ b/third-party/tommyds/tommyhashlin.h @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Linear chained hashtable. + * + * This hashtable resizes dynamically and progressively using a variation of the + * linear hashing algorithm described in http://en.wikipedia.org/wiki/Linear_hashing + * + * It starts with the minimal size of 16 buckets, it doubles the size then it + * reaches a load factor greater than 0.5 and it halves the size with a load + * factor lower than 0.125. + * + * The progressive resize is good for real-time and interactive applications + * as it makes insert and delete operations taking always the same time. + * + * For resizing it's used a dynamic array that supports access to not contigous + * segments. + * In this way we only allocate additional table segments on the heap, without + * freeing the previous table, and then not increasing the heap fragmentation. + * + * The resize takes place inside tommy_hashlin_insert() and tommy_hashlin_remove(). + * No resize is done in the tommy_hashlin_search() operation. + * + * To initialize the hashtable you have to call tommy_hashlin_init(). + * + * \code + * tommy_hashslin hashlin; + * + * tommy_hashlin_init(&hashlin); + * \endcode + * + * To insert elements in the hashtable you have to call tommy_hashlin_insert() for + * each element. + * In the insertion call you have to specify the address of the node, the + * address of the object, and the hash value of the key to use. + * The address of the object is used to initialize the tommy_node::data field + * of the node, and the hash to initialize the tommy_node::key field. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_hashlin_insert(&hashlin, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object + * \endcode + * + * To find and element in the hashtable you have to call tommy_hashtable_search() + * providing a comparison function, its argument, and the hash of the key to search. + * + * \code + * int compare(const void* arg, const void* obj) + * { + * return *(const int*)arg != ((const struct object*)obj)->value; + * } + * + * int value_to_find = 1; + * struct object* obj = tommy_hashlin_search(&hashlin, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To iterate over all the elements in the hashtable with the same key, you have to + * use tommy_hashlin_bucket() and follow the tommy_node::next pointer until NULL. + * You have also to check explicitely for the key, as the bucket may contains + * different keys. + * + * \code + * int value_to_find = 1; + * tommy_node* i = tommy_hashlin_bucket(&hashlin, tommy_inthash_u32(value_to_find)); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * if (obj->value == value_to_find) { + * printf("%d\n", obj->value); // process the object + * } + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an element from the hashtable you have to call tommy_hashlin_remove() + * providing a comparison function, its argument, and the hash of the key to search + * and remove. + * + * \code + * struct object* obj = tommy_hashlin_remove(&hashlin, compare, &value_to_remove, tommy_inthash_u32(value_to_remove)); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the hashtable you have to remove all the elements, and deinitialize + * the hashtable calling tommy_hashlin_done(). + * + * \code + * tommy_hashlin_done(&hashlin); + * \endcode + * + * If you need to iterate over all the elements in the hashtable, you can use + * tommy_hashlin_foreach() or tommy_hashlin_foreach_arg(). + * If you need a more precise control with a real iteration, you have to insert + * all the elements also in a ::tommy_list, and use the list to iterate. + * See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYHASHLIN_H +#define __TOMMYHASHLIN_H + +#include "tommyhash.h" + +/******************************************************************************/ +/* hashlin */ + +/** \internal + * Initial and minimal size of the hashtable expressed as a power of 2. + * The initial size is 2^TOMMY_HASHLIN_BIT. + */ +#define TOMMY_HASHLIN_BIT 6 + +/** + * Hashtable node. + * This is the node that you have to include inside your objects. + */ +typedef tommy_node tommy_hashlin_node; + +/** \internal + * Max number of elements as a power of 2. + */ +#define TOMMY_HASHLIN_BIT_MAX 32 + +/** + * Hashtable container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_hashlin_struct { + tommy_hashlin_node** bucket[TOMMY_HASHLIN_BIT_MAX]; /**< Dynamic array of hash buckets. One list for each hash modulus. */ + tommy_uint_t bucket_bit; /**< Bits used in the bit mask. */ + tommy_count_t bucket_max; /**< Number of buckets. */ + tommy_count_t bucket_mask; /**< Bit mask to access the buckets. */ + tommy_count_t low_max; /**< Low order max value. */ + tommy_count_t low_mask; /**< Low order mask value. */ + tommy_count_t split; /**< Split position. */ + tommy_count_t count; /**< Number of elements. */ + tommy_uint_t state; /**< Reallocation state. */ +} tommy_hashlin; + +/** + * Initializes the hashtable. + */ +void tommy_hashlin_init(tommy_hashlin* hashlin); + +/** + * Deinitializes the hashtable. + * + * You can call this function with elements still contained, + * but such elements are not going to be freed by this call. + */ +void tommy_hashlin_done(tommy_hashlin* hashlin); + +/** + * Inserts an element in the hashtable. + */ +void tommy_hashlin_insert(tommy_hashlin* hashlin, tommy_hashlin_node* node, void* data, tommy_hash_t hash); + +/** + * Searches and removes an element from the hashtable. + * You have to provide a compare function and the hash of the element you want to remove. + * If the element is not found, 0 is returned. + * If more equal elements are present, the first one is removed. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find and remove. + * \return The removed element, or 0 if not found. + */ +void* tommy_hashlin_remove(tommy_hashlin* hashlin, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash); + +/** \internal + * Returns the bucket at the specified position. + */ +tommy_inline tommy_hashlin_node** tommy_hashlin_pos(tommy_hashlin* hashlin, tommy_hash_t pos) +{ + tommy_uint_t bsr; + + /* get the highest bit set, in case of all 0, return 0 */ + bsr = tommy_ilog2_u32(pos | 1); + + return &hashlin->bucket[bsr][pos]; +} + +/** \internal + * Returns a pointer to the bucket of the specified hash. + */ +tommy_inline tommy_hashlin_node** tommy_hashlin_bucket_ref(tommy_hashlin* hashlin, tommy_hash_t hash) +{ + tommy_count_t pos; + tommy_count_t high_pos; + + pos = hash & hashlin->low_mask; + high_pos = hash & hashlin->bucket_mask; + + /* if this position is already allocated in the high half */ + if (pos < hashlin->split) { + /* The following assigment is expected to be implemented */ + /* with a conditional move instruction */ + /* that results in a little better and constant performance */ + /* regardless of the split position. */ + /* This affects mostly the worst case, when the split value */ + /* is near at its half, resulting in a totally unpredictable */ + /* condition by the CPU. */ + /* In such case the use of the conditional move is generally faster. */ + + /* use also the high bit */ + pos = high_pos; + } + + return tommy_hashlin_pos(hashlin, pos); +} + +/** + * Gets the bucket of the specified hash. + * The bucket is guaranteed to contain ALL the elements with the specified hash, + * but it can contain also others. + * You can access elements in the bucket following the ::next pointer until 0. + * \param hash Hash of the element to find. + * \return The head of the bucket, or 0 if empty. + */ +tommy_inline tommy_hashlin_node* tommy_hashlin_bucket(tommy_hashlin* hashlin, tommy_hash_t hash) +{ + return *tommy_hashlin_bucket_ref(hashlin, hash); +} + +/** + * Searches an element in the hashtable. + * You have to provide a compare function and the hash of the element you want to find. + * If more equal elements are present, the first one is returned. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_hashlin_search(tommy_hashlin* hashlin, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashlin_node* i = tommy_hashlin_bucket(hashlin, hash); + + while (i) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (i->key == hash && cmp(cmp_arg, i->data) == 0) + return i->data; + i = i->next; + } + return 0; +} + +/** + * Removes an element from the hashtable. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_hashlin_remove_existing(tommy_hashlin* hashlin, tommy_hashlin_node* node); + +/** + * Calls the specified function for each element in the hashtable. + * + * You cannot add or remove elements from the inside of the callback, + * but can use it to deallocate them. + * + * \code + * tommy_hashlin hashlin; + * + * // initializes the hashtable + * tommy_hashlin_init(&hashlin); + * + * ... + * + * // creates an object + * struct object* obj = malloc(sizeof(struct object)); + * + * ... + * + * // insert it in the hashtable + * tommy_hashlin_insert(&hashlin, &obj->node, obj, tommy_inthash_u32(obj->value)); + * + * ... + * + * // deallocates all the objects iterating the hashtable + * tommy_hashlin_foreach(&hashlin, free); + * + * // deallocates the hashtable + * tommy_hashlin_done(&hashlin); + * \endcode + */ +void tommy_hashlin_foreach(tommy_hashlin* hashlin, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the hashtable. + */ +void tommy_hashlin_foreach_arg(tommy_hashlin* hashlin, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_hashlin_count(tommy_hashlin* hashlin) +{ + return hashlin->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_hashlin_node of the stored elements. + */ +tommy_size_t tommy_hashlin_memory_usage(tommy_hashlin* hashlin); + +#endif + diff --git a/third-party/tommyds/tommyhashtbl.c b/third-party/tommyds/tommyhashtbl.c new file mode 100644 index 0000000..007b8ec --- /dev/null +++ b/third-party/tommyds/tommyhashtbl.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommyhashtbl.h" +#include "tommylist.h" + +#include <string.h> /* for memset */ + +/******************************************************************************/ +/* hashtable */ + +void tommy_hashtable_init(tommy_hashtable* hashtable, tommy_count_t bucket_max) +{ + if (bucket_max < 16) + bucket_max = 16; + else + bucket_max = tommy_roundup_pow2_u32(bucket_max); + + hashtable->bucket_max = bucket_max; + hashtable->bucket_mask = hashtable->bucket_max - 1; + + /* initialize the vector using malloc()+memset() instead of calloc() */ + /* to ensure that all the memory in really allocated immediately */ + /* by the OS, and not deferred at later time. */ + /* this improves performance, because we start with a fully initialized hashtable. */ + hashtable->bucket = tommy_cast(tommy_hashtable_node**, tommy_malloc(hashtable->bucket_max * sizeof(tommy_hashtable_node*))); + memset(hashtable->bucket, 0, hashtable->bucket_max * sizeof(tommy_hashtable_node*)); + + hashtable->count = 0; +} + +void tommy_hashtable_done(tommy_hashtable* hashtable) +{ + tommy_free(hashtable->bucket); +} + +void tommy_hashtable_insert(tommy_hashtable* hashtable, tommy_hashtable_node* node, void* data, tommy_hash_t hash) +{ + tommy_count_t pos = hash & hashtable->bucket_mask; + + tommy_list_insert_tail(&hashtable->bucket[pos], node, data); + + node->key = hash; + + ++hashtable->count; +} + +void* tommy_hashtable_remove_existing(tommy_hashtable* hashtable, tommy_hashtable_node* node) +{ + tommy_count_t pos = node->key & hashtable->bucket_mask; + + tommy_list_remove_existing(&hashtable->bucket[pos], node); + + --hashtable->count; + + return node->data; +} + +void* tommy_hashtable_remove(tommy_hashtable* hashtable, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_count_t pos = hash & hashtable->bucket_mask; + tommy_hashtable_node* node = hashtable->bucket[pos]; + + while (node) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (node->key == hash && cmp(cmp_arg, node->data) == 0) { + tommy_list_remove_existing(&hashtable->bucket[pos], node); + + --hashtable->count; + + return node->data; + } + node = node->next; + } + + return 0; +} + +void tommy_hashtable_foreach(tommy_hashtable* hashtable, tommy_foreach_func* func) +{ + tommy_count_t bucket_max = hashtable->bucket_max; + tommy_hashtable_node** bucket = hashtable->bucket; + tommy_count_t pos; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashtable_node* node = bucket[pos]; + + while (node) { + void* data = node->data; + node = node->next; + func(data); + } + } +} + +void tommy_hashtable_foreach_arg(tommy_hashtable* hashtable, tommy_foreach_arg_func* func, void* arg) +{ + tommy_count_t bucket_max = hashtable->bucket_max; + tommy_hashtable_node** bucket = hashtable->bucket; + tommy_count_t pos; + + for (pos = 0; pos < bucket_max; ++pos) { + tommy_hashtable_node* node = bucket[pos]; + + while (node) { + void* data = node->data; + node = node->next; + func(arg, data); + } + } +} + +tommy_size_t tommy_hashtable_memory_usage(tommy_hashtable* hashtable) +{ + return hashtable->bucket_max * (tommy_size_t)sizeof(hashtable->bucket[0]) + + tommy_hashtable_count(hashtable) * (tommy_size_t)sizeof(tommy_hashtable_node); +} + diff --git a/third-party/tommyds/tommyhashtbl.h b/third-party/tommyds/tommyhashtbl.h new file mode 100644 index 0000000..0cb6332 --- /dev/null +++ b/third-party/tommyds/tommyhashtbl.h @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Fixed size chained hashtable. + * + * This hashtable is a standard implementation of a chained hashtable with a fixed size. + * + * Note that performances starts to degenerate after reaching a load factor greater than 0.75. + * The ::tommy_hashdyn and ::tommy_hashlin hashtables fix this problem growing dynamically. + * + * To initialize the hashtable you have to call tommy_hashtable_init() specifing + * the fixed bucket size. + * + * \code + * tommy_hashslin hashtable; + * + * tommy_hashtable_init(&hashtable, 1024); + * \endcode + * + * To insert elements in the hashtable you have to call tommy_hashtable_insert() for + * each element. + * In the insertion call you have to specify the address of the node, the + * address of the object, and the hash value of the key to use. + * The address of the object is used to initialize the tommy_node::data field + * of the node, and the hash to initialize the tommy_node::key field. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_hashtable_insert(&hashtable, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object + * \endcode + * + * To find and element in the hashtable you have to call tommy_hashtable_search() + * providing a comparison function, its argument, and the hash of the key to search. + * + * \code + * int compare(const void* arg, const void* obj) + * { + * return *(const int*)arg != ((const struct object*)obj)->value; + * } + * + * int value_to_find = 1; + * struct object* obj = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To iterate over all the elements in the hashtable with the same key, you have to + * use tommy_hashtable_bucket() and follow the tommy_node::next pointer until NULL. + * You have also to check explicitely for the key, as the bucket may contains + * different keys. + * + * \code + * tommy_node* i = tommy_hashtable_bucket(&hashtable, tommy_inthash_u32(value_to_find)); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * if (obj->value == value_to_find) { + * printf("%d\n", obj->value); // process the object + * } + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an element from the hashtable you have to call tommy_hashtable_remove() + * providing a comparison function, its argument, and the hash of the key to search + * and remove. + * + * \code + * struct object* obj = tommy_hashtable_remove(&hashtable, compare, &value_to_remove, tommy_inthash_u32(value_to_remove)); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the hashtable you have to remove all the elements, and deinitialize + * the hashtable calling tommy_hashtable_done(). + * + * \code + * tommy_hashtable_done(&hashtable); + * \endcode + * + * If you need to iterate over all the elements in the hashtable, you can use + * tommy_hashtable_foreach() or tommy_hashtable_foreach_arg(). + * If you need a more precise control with a real iteration, you have to insert + * all the elements also in a ::tommy_list, and use the list to iterate. + * See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYHASHTBL_H +#define __TOMMYHASHTBL_H + +#include "tommyhash.h" + +/******************************************************************************/ +/* hashtable */ + +/** + * Hashtable node. + * This is the node that you have to include inside your objects. + */ +typedef tommy_node tommy_hashtable_node; + +/** + * Hashtable container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_hashtable_struct { + tommy_hashtable_node** bucket; /**< Hash buckets. One list for each hash modulus. */ + tommy_count_t bucket_max; /**< Number of buckets. */ + tommy_count_t bucket_mask; /**< Bit mask to access the buckets. */ + tommy_count_t count; /**< Number of elements. */ +} tommy_hashtable; + +/** + * Initializes the hashtable. + * \param buckets Minimum number of buckets to allocate. The effective number used is the next power of 2. + */ +void tommy_hashtable_init(tommy_hashtable* hashtable, tommy_count_t bucket_max); + +/** + * Deinitializes the hashtable. + * + * You can call this function with elements still contained, + * but such elements are not going to be freed by this call. + */ +void tommy_hashtable_done(tommy_hashtable* hashtable); + +/** + * Inserts an element in the hashtable. + */ +void tommy_hashtable_insert(tommy_hashtable* hashtable, tommy_hashtable_node* node, void* data, tommy_hash_t hash); + +/** + * Searches and removes an element from the hashtable. + * You have to provide a compare function and the hash of the element you want to remove. + * If the element is not found, 0 is returned. + * If more equal elements are present, the first one is removed. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find and remove. + * \return The removed element, or 0 if not found. + */ +void* tommy_hashtable_remove(tommy_hashtable* hashtable, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash); + +/** + * Gets the bucket of the specified hash. + * The bucket is guaranteed to contain ALL the elements with the specified hash, + * but it can contain also others. + * You can access elements in the bucket following the ::next pointer until 0. + * \param hash Hash of the element to find. + * \return The head of the bucket, or 0 if empty. + */ +tommy_inline tommy_hashtable_node* tommy_hashtable_bucket(tommy_hashtable* hashtable, tommy_hash_t hash) +{ + return hashtable->bucket[hash & hashtable->bucket_mask]; +} + +/** + * Searches an element in the hashtable. + * You have to provide a compare function and the hash of the element you want to find. + * If more equal elements are present, the first one is returned. + * \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one. + * The function should return 0 for equal elements, anything other for different elements. + * \param cmp_arg Compare argument passed as first argument of the compare function. + * \param hash Hash of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_hashtable_search(tommy_hashtable* hashtable, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash) +{ + tommy_hashtable_node* i = tommy_hashtable_bucket(hashtable, hash); + + while (i) { + /* we first check if the hash matches, as in the same bucket we may have multiples hash values */ + if (i->key == hash && cmp(cmp_arg, i->data) == 0) + return i->data; + i = i->next; + } + return 0; +} + +/** + * Removes an element from the hashtable. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_hashtable_remove_existing(tommy_hashtable* hashtable, tommy_hashtable_node* node); + +/** + * Calls the specified function for each element in the hashtable. + * + * You cannot add or remove elements from the inside of the callback, + * but can use it to deallocate them. + * + * \code + * tommy_hashtable hashtable; + * + * // initializes the hashtable + * tommy_hashtable_init(&hashtable, ...); + * + * ... + * + * // creates an object + * struct object* obj = malloc(sizeof(struct object)); + * + * ... + * + * // insert it in the hashtable + * tommy_hashdyn_insert(&hashtable, &obj->node, obj, tommy_inthash_u32(obj->value)); + * + * ... + * + * // deallocates all the objects iterating the hashtable + * tommy_hashtable_foreach(&hashtable, free); + * + * // deallocates the hashtable + * tommy_hashdyn_done(&hashtable); + * \endcode + */ +void tommy_hashtable_foreach(tommy_hashtable* hashtable, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the hashtable. + */ +void tommy_hashtable_foreach_arg(tommy_hashtable* hashtable, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_hashtable_count(tommy_hashtable* hashtable) +{ + return hashtable->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_hashtable_node of the stored elements. + */ +tommy_size_t tommy_hashtable_memory_usage(tommy_hashtable* hashtable); + +#endif + diff --git a/third-party/tommyds/tommylist.c b/third-party/tommyds/tommylist.c new file mode 100644 index 0000000..1fa1f24 --- /dev/null +++ b/third-party/tommyds/tommylist.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommylist.h" +#include "tommychain.h" + +/** \internal + * Setup a list. + */ +tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node* tail) +{ + head->prev = tail; + tail->next = 0; + *list = head; +} + +void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp) +{ + tommy_chain chain; + tommy_node* head; + + if (tommy_list_empty(list)) + return; + + head = tommy_list_head(list); + + /* create a chain from the list */ + chain.head = head; + chain.tail = head->prev; + + tommy_chain_mergesort(&chain, cmp); + + /* restore the list */ + tommy_list_set(list, chain.head, chain.tail); +} + diff --git a/third-party/tommyds/tommylist.h b/third-party/tommyds/tommylist.h new file mode 100644 index 0000000..368606a --- /dev/null +++ b/third-party/tommyds/tommylist.h @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Double linked list for collisions into hashtables. + * + * This list is a double linked list mainly targetted for handling collisions + * into an hashtables, but useable also as a generic list. + * + * The main feature of this list is to require only one pointer to represent the + * list, compared to a classic implementation requiring a head an a tail pointers. + * This reduces the memory usage in hashtables. + * + * Another feature is to support the insertion at the end of the list. This allow to store + * collisions in a stable order. Where for stable order we mean that equal elements keep + * their insertion order. + * + * To initialize the list, you have to call tommy_list_init(), or to simply assign + * to it NULL, as an empty list is represented by the NULL value. + * + * \code + * tommy_list list; + * + * tommy_list_init(&list); // initializes the list + * \endcode + * + * To insert elements in the list you have to call tommy_list_insert_tail() + * or tommy_list_insert_head() for each element. + * In the insertion call you have to specify the address of the node and the + * address of the object. + * The address of the object is used to initialize the tommy_node::data field + * of the node. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object + * \endcode + * + * To iterate over all the elements in the list you have to call + * tommy_list_head() to get the head of the list and follow the + * tommy_node::next pointer until NULL. + * + * \code + * tommy_node* i = tommy_list_head(&list); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * printf("%d\n", obj->value); // process the object + * + * i = i->next; // go to the next element + * } + * \endcode + * + * To destroy the list you have to remove all the elements, + * as the list is completely inplace and it doesn't allocate memory. + * This can be done with the tommy_list_foreach() function. + * + * \code + * // deallocates all the objects iterating the list + * tommy_list_foreach(&list, free); + * \endcode + */ + +#ifndef __TOMMYLIST_H +#define __TOMMYLIST_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* list */ + +/** + * Double linked list type. + */ +typedef tommy_node* tommy_list; + +/** + * Initializes the list. + * The list is completely inplace, so it doesn't need to be deinitialized. + */ +tommy_inline void tommy_list_init(tommy_list* list) +{ + *list = 0; +} + +/** + * Gets the head of the list. + * \return The head node. For empty lists 0 is returned. + */ +tommy_inline tommy_node* tommy_list_head(tommy_list* list) +{ + return *list; +} + +/** + * Gets the tail of the list. + * \return The tail node. For empty lists 0 is returned. + */ +tommy_inline tommy_node* tommy_list_tail(tommy_list* list) +{ + tommy_node* head = tommy_list_head(list); + + if (!head) + return 0; + + return head->prev; +} + +/** \internal + * Creates a new list with a single element. + * \param list The list to initialize. + * \param node The node to insert. + */ +tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node) +{ + /* one element "circular" prev list */ + node->prev = node; + + /* one element "0 terminated" next list */ + node->next = 0; + + *list = node; +} + +/** \internal + * Inserts an element at the head of a not empty list. + * The element is inserted at the head of the list. The list cannot be empty. + * \param list The list. The list cannot be empty. + * \param node The node to insert. + */ +tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node) +{ + tommy_node* head = tommy_list_head(list); + + /* insert in the "circular" prev list */ + node->prev = head->prev; + head->prev = node; + + /* insert in the "0 terminated" next list */ + node->next = head; + + *list = node; +} + +/** \internal + * Inserts an element at the tail of a not empty list. + * The element is inserted at the tail of the list. The list cannot be empty. + * \param head The node at the list head. It cannot be 0. + * \param node The node to insert. + */ +tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node* node) +{ + /* insert in the "circular" prev list */ + node->prev = head->prev; + head->prev = node; + + /* insert in the "0 terminated" next list */ + node->next = 0; + node->prev->next = node; +} + +/** + * Inserts an element at the head of a list. + * \param node The node to insert. + * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + */ +tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data) +{ + tommy_node* head = tommy_list_head(list); + + if (head) + tommy_list_insert_head_not_empty(list, node); + else + tommy_list_insert_first(list, node); + + node->data = data; +} + +/** + * Inserts an element at the tail of a list. + * \param node The node to insert. + * \param data The object containing the node. It's used to set the tommy_node::data field of the node. + */ +tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data) +{ + tommy_node* head = tommy_list_head(list); + + if (head) + tommy_list_insert_tail_not_empty(head, node); + else + tommy_list_insert_first(list, node); + + node->data = data; +} + +/** + * Removes an element from the list. + * You must already have the address of the element to remove. + * \note The node content is left unchanged, including the tommy_node::next + * and tommy_node::prev fields that still contain pointers at the list. + * \param node The node to remove. The node must be in the list. + * \return The tommy_node::data field of the node removed. + */ +tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node) +{ + tommy_node* head = tommy_list_head(list); + + /* remove from the "circular" prev list */ + if (node->next) + node->next->prev = node->prev; + else + head->prev = node->prev; /* the last */ + + /* remove from the "0 terminated" next list */ + if (head == node) + *list = node->next; /* the new head, in case 0 */ + else + node->prev->next = node->next; + + return node->data; +} + +/** + * Concats two lists. + * The second list is concatenated at the first list. + * \param first The first list. + * \param second The second list. After this call the list content is undefined, + * and you should not use it anymore. + */ +tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second) +{ + tommy_node* first_head; + tommy_node* first_tail; + tommy_node* second_head; + + /* if the second is empty, nothing to do */ + second_head = tommy_list_head(second); + if (second_head == 0) + return; + + /* if the first is empty, copy the second */ + first_head = tommy_list_head(first); + if (first_head == 0) { + *first = *second; + return; + } + + /* tail of the first list */ + first_tail = first_head->prev; + + /* set the "circular" prev list */ + first_head->prev = second_head->prev; + second_head->prev = first_tail; + + /* set the "0 terminated" next list */ + first_tail->next = second_head; +} + +/** + * Sorts a list. + * It's a stable merge sort with O(N*log(N)) worst complexity. + * It's faster on degenerated cases like partially ordered lists. + * \param cmp Compare function called with two elements. + * The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greather. + */ +void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp); + +/** + * Checks if empty. + * \return If the list is empty. + */ +tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list) +{ + return tommy_list_head(list) == 0; +} + +/** + * Gets the number of elements. + * \note This operation is O(n). + */ +tommy_inline tommy_count_t tommy_list_count(tommy_list* list) +{ + tommy_count_t count = 0; + tommy_node* i = tommy_list_head(list); + + while (i) { + ++count; + i = i->next; + } + + return count; +} + +/** + * Calls the specified function for each element in the list. + * + * You cannot add or remove elements from the inside of the callback, + * but can use it to deallocate them. + * + * \code + * tommy_list list; + * + * // initializes the list + * tommy_list_init(&list); + * + * ... + * + * // creates an object + * struct object* obj = malloc(sizeof(struct object)); + * + * ... + * + * // insert it in the list + * tommy_list_insert_tail(&list, &obj->node, obj); + * + * ... + * + * // deallocates all the objects iterating the list + * tommy_list_foreach(&list, free); + * \endcode + */ +tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func) +{ + tommy_node* node = tommy_list_head(list); + + while (node) { + void* data = node->data; + node = node->next; + func(data); + } +} + +/** + * Calls the specified function with an argument for each element in the list. + */ +tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg) +{ + tommy_node* node = tommy_list_head(list); + + while (node) { + void* data = node->data; + node = node->next; + func(arg, data); + } +} + +#endif + diff --git a/third-party/tommyds/tommytree.c b/third-party/tommyds/tommytree.c new file mode 100644 index 0000000..6b2f1a9 --- /dev/null +++ b/third-party/tommyds/tommytree.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2015, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommytree.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* tree */ + +void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp) +{ + tree->root = 0; + tree->count = 0; + tree->cmp = cmp; +} + +static int tommy_tree_delta(tommy_tree_node* root) +{ + int left_height = root->prev ? root->prev->key : 0; + int right_height = root->next ? root->next->key : 0; + + return left_height - right_height; +} + +/* AVL tree operations */ +static tommy_tree_node* tommy_tree_balance(tommy_tree_node*); + +static tommy_tree_node* tommy_tree_rotate_left(tommy_tree_node* root) +{ + tommy_tree_node* next = root->next; + + root->next = next->prev; + + next->prev = tommy_tree_balance(root); + + return tommy_tree_balance(next); +} + +static tommy_tree_node* tommy_tree_rotate_right(tommy_tree_node* root) +{ + tommy_tree_node* prev = root->prev; + + root->prev = prev->next; + + prev->next = tommy_tree_balance(root); + + return tommy_tree_balance(prev); +} + +static tommy_tree_node* tommy_tree_move_right(tommy_tree_node* root, tommy_tree_node* node) +{ + if (!root) + return node; + + root->next = tommy_tree_move_right(root->next, node); + + return tommy_tree_balance(root); +} + +static tommy_tree_node* tommy_tree_balance(tommy_tree_node* root) +{ + int delta = tommy_tree_delta(root); + + if (delta < -1) { + if (tommy_tree_delta(root->next) > 0) + root->next = tommy_tree_rotate_right(root->next); + return tommy_tree_rotate_left(root); + } + + if (delta > 1) { + if (tommy_tree_delta(root->prev) < 0) + root->prev = tommy_tree_rotate_left(root->prev); + return tommy_tree_rotate_right(root); + } + + /* recompute key */ + root->key = 0; + + if (root->prev && root->prev->key > root->key) + root->key = root->prev->key; + + if (root->next && root->next->key > root->key) + root->key = root->next->key; + + /* count itself */ + root->key += 1; + + return root; +} + +static tommy_tree_node* tommy_tree_insert_node(tommy_compare_func* cmp, tommy_tree_node* root, tommy_tree_node** let) +{ + int c; + + if (!root) + return *let; + + c = cmp((*let)->data, root->data); + + if (c < 0) { + root->prev = tommy_tree_insert_node(cmp, root->prev, let); + return tommy_tree_balance(root); + } + + if (c > 0) { + root->next = tommy_tree_insert_node(cmp, root->next, let); + return tommy_tree_balance(root); + } + + /* already present, set the return pointer */ + *let = root; + + return root; +} + +void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data) +{ + tommy_tree_node* insert = node; + + insert->data = data; + insert->prev = 0; + insert->next = 0; + insert->key = 0; + + tree->root = tommy_tree_insert_node(tree->cmp, tree->root, &insert); + + if (insert == node) + ++tree->count; + + return insert->data; +} + +static tommy_tree_node* tommy_tree_remove_node(tommy_compare_func* cmp, tommy_tree_node* root, void* data, tommy_tree_node** let) +{ + int c; + + if (!root) + return 0; + + c = cmp(data, root->data); + + if (c < 0) { + root->prev = tommy_tree_remove_node(cmp, root->prev, data, let); + return tommy_tree_balance(root); + } + + if (c > 0) { + root->next = tommy_tree_remove_node(cmp, root->next, data, let); + return tommy_tree_balance(root); + } + + /* found */ + *let = root; + + return tommy_tree_move_right(root->prev, root->next); +} + +void* tommy_tree_remove(tommy_tree* tree, void* data) +{ + tommy_tree_node* node = 0; + + tree->root = tommy_tree_remove_node(tree->cmp, tree->root, data, &node); + + if (!node) + return 0; + + --tree->count; + + return node->data; +} + +static tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* root, void* data) +{ + int c; + + if (!root) + return 0; + + c = cmp(data, root->data); + + if (c < 0) + return tommy_tree_search_node(cmp, root->prev, data); + + if (c > 0) + return tommy_tree_search_node(cmp, root->next, data); + + return root; +} + +void* tommy_tree_search(tommy_tree* tree, void* data) +{ + tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data); + + if (!node) + return 0; + + return node->data; +} + +void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg) +{ + tommy_tree_node* node = tommy_tree_search_node(cmp, tree->root, cmp_arg); + + if (!node) + return 0; + + return node->data; +} + +void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node) +{ + void* data = tommy_tree_remove(tree, node->data); + + assert(data != 0); + + return data; +} + +static void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func) +{ + tommy_tree_node* next; + + if (!root) + return; + + tommy_tree_foreach_node(root->prev, func); + + /* make a copy in case func is free() */ + next = root->next; + + func(root->data); + + tommy_tree_foreach_node(next, func); +} + +void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func) +{ + tommy_tree_foreach_node(tree->root, func); +} + +static void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg) +{ + tommy_tree_node* next; + + if (!root) + return; + + tommy_tree_foreach_arg_node(root->prev, func, arg); + + /* make a copy in case func is free() */ + next = root->next; + + func(arg, root->data); + + tommy_tree_foreach_arg_node(next, func, arg); +} + +void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg) +{ + tommy_tree_foreach_arg_node(tree->root, func, arg); +} + +tommy_size_t tommy_tree_memory_usage(tommy_tree* tree) +{ + return tommy_tree_count(tree) * sizeof(tommy_tree_node); +} + diff --git a/third-party/tommyds/tommytree.h b/third-party/tommyds/tommytree.h new file mode 100644 index 0000000..55e4006 --- /dev/null +++ b/third-party/tommyds/tommytree.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2015, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * AVL tree. + * + * This tree is a standard AVL tree implementation that stores elements in the + * order defined by the comparison function. + * + * As difference than other tommy containers, duplicate elements cannot be inserted. + * + * To initialize a tree you have to call tommy_tree_init() specifing a comparison + * function that will define the order in the tree. + * + * \code + * tommy_tree tree; + * + * tommy_tree_init(&tree, cmp); + * \endcode + * + * To insert elements in the tree you have to call tommy_tree_insert() for + * each element. + * In the insertion call you have to specify the address of the node and the + * address of the object. + * The address of the object is used to initialize the tommy_node::data field + * of the node. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_tree_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_tree_insert(&tree, &obj->node, obj); // inserts the object + * \endcode + * + * To find and element in the tree you have to call tommy_tree_search() providing + * the key to search. + * + * \code + * struct object value_to_find = { 1 }; + * struct object* obj = tommy_tree_search(&tree, &value_to_find); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To remove an element from the tree you have to call tommy_tree_remove() + * providing the key to search and remove. + * + * \code + * struct object value_to_remove = { 1 }; + * struct object* obj = tommy_tree_remove(&tree, &value_to_remove); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the tree you have to remove or destroy all the contained elements. + * The tree itself doesn't have or need a deallocation function. + * + * If you need to iterate over all the elements in the tree, you can use + * tommy_tree_foreach() or tommy_tree_foreach_arg(). + * If you need a more precise control with a real iteration, you have to insert + * all the elements also in a ::tommy_list, and use the list to iterate. + * See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYTREE_H +#define __TOMMYTREE_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* tree */ + +/** + * Tree node. + * This is the node that you have to include inside your objects. + */ +typedef tommy_node tommy_tree_node; + +/** + * Tree container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_tree_struct { + tommy_tree_node* root; /**< Root node. */ + tommy_count_t count; /**< Number of elements. */ + tommy_compare_func* cmp; /**< Comparison function. */ +} tommy_tree; + +/** + * Initializes the tree. + * \param cmp The comparison function that defines the orderin the tree. + */ +void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp); + +/** + * Inserts an element in the tree. + * If the element is already present, it's not inserted again. + * Check the return value to identify if the element was already present or not. + * You have to provide the pointer of the node embedded into the object and + * the pointer to the object. + * \param node Pointer to the node embedded into the object to insert. + * \param data Pointer to the object to insert. + * \return The element in the tree. Either the already existing one, or the one just inserted. + */ +void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data); + +/** + * Searches and removes an element. + * If the element is not found, 0 is returned. + * \param data Element used for comparison. + * \return The removed element, or 0 if not found. + */ +void* tommy_tree_remove(tommy_tree* tree, void* data); + +/** + * Searches an element in the tree. + * If the element is not found, 0 is returned. + * \param data Element used for comparison. + * \return The first element found, or 0 if none. + */ +void* tommy_tree_search(tommy_tree* tree, void* data); + +/** + * Searches an element in the tree with a specific comparison function. + * + * Like tommy_tree_search() but you can specify a different comparison function. + * Note that this function must define a suborder of the original one. + * + * The ::data argument will be the first argument of the comparison function, + * and it can be of a different type of the objects in the tree. + */ +void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg); + +/** + * Removes an element from the tree. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node); + +/** + * Calls the specified function for each element in the tree. + * + * The elements are processed in order. + * + * You cannot add or remove elements from the inside of the callback, + * but can use it to deallocate them. + * + * \code + * tommy_tree tree; + * + * // initializes the tree + * tommy_tree_init(&tree, cmp); + * + * ... + * + * // creates an object + * struct object* obj = malloc(sizeof(struct object)); + * + * ... + * + * // insert it in the tree + * tommy_tree_insert(&tree, &obj->node, obj); + * + * ... + * + * // deallocates all the objects iterating the tree + * tommy_tree_foreach(&tree, free); + * \endcode + */ +void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func); + +/** + * Calls the specified function with an argument for each element in the tree. + */ +void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_tree_count(tommy_tree* tree) +{ + return tree->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_tree_node of the stored elements. + */ +tommy_size_t tommy_tree_memory_usage(tommy_tree* tree); + +#endif + diff --git a/third-party/tommyds/tommytrie.c b/third-party/tommyds/tommytrie.c new file mode 100644 index 0000000..35ac219 --- /dev/null +++ b/third-party/tommyds/tommytrie.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommytrie.h" +#include "tommylist.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* trie */ + +/** + * Mask for the inner branches. + */ +#define TOMMY_TRIE_TREE_MASK (TOMMY_TRIE_TREE_MAX - 1) + +/** + * Shift for the first level of branches. + */ +#define TOMMY_TRIE_BUCKET_SHIFT (TOMMY_KEY_BIT - TOMMY_TRIE_BUCKET_BIT) + +/** + * Max number of levels. + */ +#define TOMMY_TRIE_LEVEL_MAX ((TOMMY_KEY_BIT - TOMMY_TRIE_BUCKET_BIT) / TOMMY_TRIE_TREE_BIT) + +/** + * Hashtrie tree. + * A tree contains TOMMY_TRIE_TREE_MAX ordered pointers to <null/node/tree>. + * + * Each tree level uses exactly TOMMY_TRIE_TREE_BIT bits from the key. + */ +struct tommy_trie_tree_struct { + tommy_trie_node* map[TOMMY_TRIE_TREE_MAX]; +}; +typedef struct tommy_trie_tree_struct tommy_trie_tree; + +/** + * Kinds of an trie node. + */ +#define TOMMY_TRIE_TYPE_NODE 0 /**< The node is of type ::tommy_trie_node. */ +#define TOMMY_TRIE_TYPE_TREE 1 /**< The node is of type ::tommy_trie_tree. */ + +/** + * Get and set pointer of trie nodes. + * + * The pointer type is stored in the lower bit. + */ +#define trie_get_type(ptr) (((tommy_uintptr_t)(ptr)) & 1) +#define trie_get_tree(ptr) ((tommy_trie_tree*)(((tommy_uintptr_t)(ptr)) - TOMMY_TRIE_TYPE_TREE)) +#define trie_set_tree(ptr) (void*)(((tommy_uintptr_t)(ptr)) + TOMMY_TRIE_TYPE_TREE) + +void tommy_trie_init(tommy_trie* trie, tommy_allocator* alloc) +{ + tommy_uint_t i; + + for (i = 0; i < TOMMY_TRIE_BUCKET_MAX; ++i) + trie->bucket[i] = 0; + + trie->count = 0; + trie->node_count = 0; + + trie->alloc = alloc; +} + +static void trie_bucket_insert(tommy_trie* trie, tommy_uint_t shift, tommy_trie_node** let_ptr, tommy_trie_node* insert, tommy_key_t key) +{ + tommy_trie_tree* tree; + tommy_trie_node* node; + void* ptr; + tommy_uint_t i; + tommy_uint_t j; + +recurse: + ptr = *let_ptr; + + /* if null, just insert the node */ + if (!ptr) { + /* setup the node as a list */ + tommy_list_insert_first(let_ptr, insert); + return; + } + + if (trie_get_type(ptr) == TOMMY_TRIE_TYPE_TREE) { + /* repeat the process one level down */ + let_ptr = &trie_get_tree(ptr)->map[(key >> shift) & TOMMY_TRIE_TREE_MASK]; + shift -= TOMMY_TRIE_TREE_BIT; + goto recurse; + } + + node = tommy_cast(tommy_trie_node*, ptr); + + /* if it's the same key, insert in the list */ + if (node->key == key) { + tommy_list_insert_tail_not_empty(node, insert); + return; + } + +expand: + /* convert to a tree */ + tree = tommy_cast(tommy_trie_tree*, tommy_allocator_alloc(trie->alloc)); + ++trie->node_count; + *let_ptr = tommy_cast(tommy_trie_node*, trie_set_tree(tree)); + + /* initialize it */ + for (i = 0; i < TOMMY_TRIE_TREE_MAX; ++i) + tree->map[i] = 0; + + /* get the position of the two elements */ + i = (node->key >> shift) & TOMMY_TRIE_TREE_MASK; + j = (key >> shift) & TOMMY_TRIE_TREE_MASK; + + /* if they don't collide */ + if (i != j) { + /* insert the already existing element */ + tree->map[i] = node; + + /* insert the new node */ + tommy_list_insert_first(&tree->map[j], insert); + return; + } + + /* expand one more level */ + let_ptr = &tree->map[i]; + shift -= TOMMY_TRIE_TREE_BIT; + goto expand; +} + +void tommy_trie_insert(tommy_trie* trie, tommy_trie_node* node, void* data, tommy_key_t key) +{ + tommy_trie_node** let_ptr; + + node->data = data; + node->key = key; + + let_ptr = &trie->bucket[key >> TOMMY_TRIE_BUCKET_SHIFT]; + + trie_bucket_insert(trie, TOMMY_TRIE_BUCKET_SHIFT, let_ptr, node, key); + + ++trie->count; +} + +static tommy_trie_node* trie_bucket_remove_existing(tommy_trie* trie, tommy_uint_t shift, tommy_trie_node** let_ptr, tommy_trie_node* remove, tommy_key_t key) +{ + tommy_trie_node* node; + tommy_trie_tree* tree; + void* ptr; + tommy_trie_node** let_back[TOMMY_TRIE_LEVEL_MAX + 1]; + tommy_uint_t level; + tommy_uint_t i; + tommy_uint_t count; + tommy_uint_t last; + + level = 0; +recurse: + ptr = *let_ptr; + + if (!ptr) + return 0; + + if (trie_get_type(ptr) == TOMMY_TRIE_TYPE_TREE) { + tree = trie_get_tree(ptr); + + /* save the path */ + let_back[level++] = let_ptr; + + /* go down one level */ + let_ptr = &tree->map[(key >> shift) & TOMMY_TRIE_TREE_MASK]; + shift -= TOMMY_TRIE_TREE_BIT; + + goto recurse; + } + + node = tommy_cast(tommy_trie_node*, ptr); + + /* if the node to remove is not specified */ + if (!remove) { + /* remove the first */ + remove = node; + + /* check if it's really the element to remove */ + if (remove->key != key) + return 0; + } + + tommy_list_remove_existing(let_ptr, remove); + + /* if the list is not empty, try to reduce */ + if (*let_ptr || !level) + return remove; + +reduce: + /* go one level up */ + let_ptr = let_back[--level]; + + tree = trie_get_tree(*let_ptr); + + /* check if there is only one child node */ + count = 0; + last = 0; + for (i = 0; i < TOMMY_TRIE_TREE_MAX; ++i) { + if (tree->map[i]) { + /* if we have a sub tree, we cannot reduce */ + if (trie_get_type(tree->map[i]) != TOMMY_TRIE_TYPE_NODE) + return remove; + /* if more than one node, we cannot reduce */ + if (++count > 1) + return remove; + last = i; + } + } + + /* here count is never 0, as we cannot have a tree with only one sub node */ + assert(count == 1); + + *let_ptr = tree->map[last]; + + tommy_allocator_free(trie->alloc, tree); + --trie->node_count; + + /* repeat until more level */ + if (level) + goto reduce; + + return remove; +} + +void* tommy_trie_remove(tommy_trie* trie, tommy_key_t key) +{ + tommy_trie_node* ret; + tommy_trie_node** let_ptr; + + let_ptr = &trie->bucket[key >> TOMMY_TRIE_BUCKET_SHIFT]; + + ret = trie_bucket_remove_existing(trie, TOMMY_TRIE_BUCKET_SHIFT, let_ptr, 0, key); + + if (!ret) + return 0; + + --trie->count; + + return ret->data; +} + +void* tommy_trie_remove_existing(tommy_trie* trie, tommy_trie_node* node) +{ + tommy_trie_node* ret; + tommy_key_t key = node->key; + tommy_trie_node** let_ptr; + + let_ptr = &trie->bucket[key >> TOMMY_TRIE_BUCKET_SHIFT]; + + ret = trie_bucket_remove_existing(trie, TOMMY_TRIE_BUCKET_SHIFT, let_ptr, node, key); + + /* the element removed must match the one passed */ + assert(ret == node); + + --trie->count; + + return ret->data; +} + +tommy_trie_node* tommy_trie_bucket(tommy_trie* trie, tommy_key_t key) +{ + tommy_trie_node* node; + void* ptr; + tommy_uint_t type; + tommy_uint_t shift; + + ptr = trie->bucket[key >> TOMMY_TRIE_BUCKET_SHIFT]; + + shift = TOMMY_TRIE_BUCKET_SHIFT; + +recurse: + if (!ptr) + return 0; + + type = trie_get_type(ptr); + + switch (type) { + case TOMMY_TRIE_TYPE_NODE : + node = tommy_cast(tommy_trie_node*, ptr); + if (node->key != key) + return 0; + return node; + default : + case TOMMY_TRIE_TYPE_TREE : + ptr = trie_get_tree(ptr)->map[(key >> shift) & TOMMY_TRIE_TREE_MASK]; + shift -= TOMMY_TRIE_TREE_BIT; + goto recurse; + } +} + +tommy_size_t tommy_trie_memory_usage(tommy_trie* trie) +{ + return tommy_trie_count(trie) * (tommy_size_t)sizeof(tommy_trie_node) + + trie->node_count * (tommy_size_t)TOMMY_TRIE_BLOCK_SIZE; +} + diff --git a/third-party/tommyds/tommytrie.h b/third-party/tommyds/tommytrie.h new file mode 100644 index 0000000..d82a5f2 --- /dev/null +++ b/third-party/tommyds/tommytrie.h @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Trie optimized for cache utilization. + * + * This trie is a standard implementation that stores elements in the order defined + * by the key. + * + * It needs an external allocator for the inner nodes in the trie. + * + * You can control the number of branches of each node using the ::TOMMY_TRIE_TREE_MAX + * define. More branches imply more speed, but a bigger memory occupation. + * + * Compared to ::tommy_trie_inplace you have to provide a ::tommy_allocator allocator. + * Note that the C malloc() is too slow to futfill this role. + * + * To initialize the trie you have to call tommy_allocator_init() to initialize + * the allocator, and tommy_trie_init() for the trie. + * + * \code + * tommy_allocator alloc; + * tommy_trie trie; + * + * tommy_allocator_init(&alloc, TOMMY_TRIE_BLOCK_SIZE, TOMMY_TRIE_BLOCK_SIZE); + * + * tommy_trie_init(&trie, &alloc); + * \endcode + * + * To insert elements in the trie you have to call tommy_trie_insert() for + * each element. + * In the insertion call you have to specify the address of the node, the + * address of the object, and the key value to use. + * The address of the object is used to initialize the tommy_node::data field + * of the node, and the key to initialize the tommy_node::key field. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_trie_insert(&trie, &obj->node, obj, obj->value); // inserts the object + * \endcode + * + * To find and element in the trie you have to call tommy_trie_search() providing + * the key to search. + * + * \code + * int value_to_find = 1; + * struct object* obj = tommy_trie_search(&trie, value_to_find); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To iterate over all the elements in the trie with the same key, you have to + * use tommy_trie_bucket() and follow the tommy_node::next pointer until NULL. + * + * \code + * int value_to_find = 1; + * tommy_node* i = tommy_trie_bucket(&trie, value_to_find); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * printf("%d\n", obj->value); // process the object + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an element from the trie you have to call tommy_trie_remove() + * providing the key to search and remove. + * + * \code + * struct object* obj = tommy_trie_remove(&trie, value_to_remove); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the trie you have to remove all the elements, and deinitialize + * the allocator using tommy_allocator_done(). + * + * \code + * tommy_allocator_done(&alloc); + * \endcode + * + * Note that you cannot iterate over all the elements in the trie using the + * trie itself. You have to insert all the elements also in a ::tommy_list, + * and use the list to iterate. See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYTRIE_H +#define __TOMMYTRIE_H + +#include "tommytypes.h" +#include "tommyalloc.h" + +/******************************************************************************/ +/* trie */ + +/** + * Number of branches on each inner node. It must be a power of 2. + * Suggested values are 8, 16 and 32. + * Any inner node, excluding leafs, contains a pointer to each branch. + * + * The default size is choosen to exactly fit a typical cache line of 64 bytes. + */ +#define TOMMY_TRIE_TREE_MAX (64 / sizeof(void*)) + +/** + * Trie block size. + * You must use this value to initialize the allocator. + */ +#define TOMMY_TRIE_BLOCK_SIZE (TOMMY_TRIE_TREE_MAX * sizeof(void*)) + +/** \internal + * Number of bits for each branch. + */ +#define TOMMY_TRIE_TREE_BIT TOMMY_ILOG2(TOMMY_TRIE_TREE_MAX) + +/** \internal + * Number of bits of the first level. + */ +#define TOMMY_TRIE_BUCKET_BIT ((TOMMY_KEY_BIT % TOMMY_TRIE_TREE_BIT) + TOMMY_TRIE_TREE_BIT) + +/** \internal + * Number of branches of the first level. + * It's like a inner branch, but bigger to get any remainder bits. + */ +#define TOMMY_TRIE_BUCKET_MAX (1 << TOMMY_TRIE_BUCKET_BIT) + +/** + * Trie node. + * This is the node that you have to include inside your objects. + */ +typedef tommy_node tommy_trie_node; + +/** + * Trie container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_trie_struct { + tommy_trie_node* bucket[TOMMY_TRIE_BUCKET_MAX]; /**< First tree level. */ + tommy_count_t count; /**< Number of elements. */ + tommy_count_t node_count; /**< Number of nodes. */ + tommy_allocator* alloc; /**< Allocator for internal nodes. */ +} tommy_trie; + +/** + * Initializes the trie. + * You have to provide an allocator initialized with *both* the size and align with TOMMY_TRIE_BLOCK_SIZE. + * You can share this allocator with other tries. + * + * The tries is completely allocated through the allocator, and it doesn't need to be deinitialized. + * \param alloc Allocator initialized with *both* the size and align with TOMMY_TRIE_BLOCK_SIZE. + */ +void tommy_trie_init(tommy_trie* trie, tommy_allocator* alloc); + +/** + * Inserts an element in the trie. + * You have to provide the pointer of the node embedded into the object, + * the pointer to the object and the key to use. + * \param node Pointer to the node embedded into the object to insert. + * \param data Pointer to the object to insert. + * \param key Key to use to insert the object. + */ +void tommy_trie_insert(tommy_trie* trie, tommy_trie_node* node, void* data, tommy_key_t key); + +/** + * Searches and removes the first element with the specified key. + * If the element is not found, 0 is returned. + * If more equal elements are present, the first one is removed. + * This operation is faster than calling tommy_trie_bucket() and tommy_trie_remove_existing() separately. + * \param key Key of the element to find and remove. + * \return The removed element, or 0 if not found. + */ +void* tommy_trie_remove(tommy_trie* trie, tommy_key_t key); + +/** + * Gets the bucket of the specified key. + * The bucket is guaranteed to contain ALL and ONLY the elements with the specified key. + * You can access elements in the bucket following the ::next pointer until 0. + * \param key Key of the element to find. + * \return The head of the bucket, or 0 if empty. + */ +tommy_trie_node* tommy_trie_bucket(tommy_trie* trie, tommy_key_t key); + +/** + * Searches an element in the trie. + * You have to provide the key of the element you want to find. + * If more elements with the same key are present, the first one is returned. + * \param key Key of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_trie_search(tommy_trie* trie, tommy_key_t key) +{ + tommy_trie_node* i = tommy_trie_bucket(trie, key); + + if (!i) + return 0; + + return i->data; +} + +/** + * Removes an element from the trie. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_trie_remove_existing(tommy_trie* trie, tommy_trie_node* node); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_trie_count(tommy_trie* trie) +{ + return trie->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_trie_node of the stored elements. + */ +tommy_size_t tommy_trie_memory_usage(tommy_trie* trie); + +#endif + diff --git a/third-party/tommyds/tommytrieinp.c b/third-party/tommyds/tommytrieinp.c new file mode 100644 index 0000000..26ec2c3 --- /dev/null +++ b/third-party/tommyds/tommytrieinp.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tommytrieinp.h" + +#include <assert.h> /* for assert */ + +/******************************************************************************/ +/* trie_inplace */ + +/** + * Mask for the inner branches. + */ +#define TOMMY_TRIE_INPLACE_TREE_MASK (TOMMY_TRIE_INPLACE_TREE_MAX - 1) + +/** + * Shift for the first level of branches. + */ +#define TOMMY_TRIE_INPLACE_BUCKET_SHIFT (TOMMY_KEY_BIT - TOMMY_TRIE_INPLACE_BUCKET_BIT) + +/** + * Create a new list with a single element. + */ +tommy_inline tommy_trie_inplace_node* tommy_trie_inplace_list_insert_first(tommy_trie_inplace_node* node) +{ + /* one element "circular" prev list */ + node->prev = node; + + /* one element "0 terminated" next list */ + node->next = 0; + + return node; +} + +/** + * Add an element to an existing list. + * \note The element is inserted at the end of the list. + */ +tommy_inline void tommy_trie_inplace_list_insert_tail_not_empty(tommy_trie_inplace_node* head, tommy_trie_inplace_node* node) +{ + /* insert in the list in the last position */ + + /* insert in the "circular" prev list */ + node->prev = head->prev; + head->prev = node; + + /* insert in the "0 terminated" next list */ + node->next = 0; + node->prev->next = node; +} + +/** + * Remove an element from the list. + */ +tommy_inline void tommy_trie_inplace_list_remove(tommy_trie_inplace_node** let_ptr, tommy_trie_inplace_node* node) +{ + tommy_trie_inplace_node* head = *let_ptr; + + /* remove from the "circular" prev list */ + if (node->next) + node->next->prev = node->prev; + else + head->prev = node->prev; /* the last */ + + /* remove from the "0 terminated" next list */ + if (head == node) + *let_ptr = node->next; /* the new first */ + else + node->prev->next = node->next; +} + +void tommy_trie_inplace_init(tommy_trie_inplace* trie_inplace) +{ + tommy_uint_t i; + + for (i = 0; i < TOMMY_TRIE_INPLACE_BUCKET_MAX; ++i) + trie_inplace->bucket[i] = 0; + + trie_inplace->count = 0; +} + +static void trie_inplace_bucket_insert(tommy_uint_t shift, tommy_trie_inplace_node** let_ptr, tommy_trie_inplace_node* insert, tommy_key_t key) +{ + tommy_trie_inplace_node* node; + + node = *let_ptr; + while (node && node->key != key) { + let_ptr = &node->map[(key >> shift) & TOMMY_TRIE_INPLACE_TREE_MASK]; + node = *let_ptr; + shift -= TOMMY_TRIE_INPLACE_TREE_BIT; + } + + /* if null, just insert the node */ + if (!node) { + /* setup the node as a list */ + *let_ptr = tommy_trie_inplace_list_insert_first(insert); + } else { + /* if it's the same key, insert in the list */ + tommy_trie_inplace_list_insert_tail_not_empty(node, insert); + } +} + +void tommy_trie_inplace_insert(tommy_trie_inplace* trie_inplace, tommy_trie_inplace_node* node, void* data, tommy_key_t key) +{ + tommy_trie_inplace_node** let_ptr; + tommy_uint_t i; + + node->data = data; + node->key = key; + /* clear the child pointers */ + for (i = 0; i < TOMMY_TRIE_INPLACE_TREE_MAX; ++i) + node->map[i] = 0; + + let_ptr = &trie_inplace->bucket[key >> TOMMY_TRIE_INPLACE_BUCKET_SHIFT]; + + trie_inplace_bucket_insert(TOMMY_TRIE_INPLACE_BUCKET_SHIFT, let_ptr, node, key); + + ++trie_inplace->count; +} + +static tommy_trie_inplace_node* trie_inplace_bucket_remove(tommy_uint_t shift, tommy_trie_inplace_node** let_ptr, tommy_trie_inplace_node* remove, tommy_key_t key) +{ + tommy_trie_inplace_node* node; + int i; + tommy_trie_inplace_node** leaf_let_ptr; + tommy_trie_inplace_node* leaf; + + node = *let_ptr; + while (node && node->key != key) { + let_ptr = &node->map[(key >> shift) & TOMMY_TRIE_INPLACE_TREE_MASK]; + node = *let_ptr; + shift -= TOMMY_TRIE_INPLACE_TREE_BIT; + } + + if (!node) + return 0; + + /* if the node to remove is not specified */ + if (!remove) + remove = node; /* remove the first */ + + tommy_trie_inplace_list_remove(let_ptr, remove); + + /* if not change in the node, nothing more to do */ + if (*let_ptr == node) + return remove; + + /* if we have a substitute */ + if (*let_ptr != 0) { + /* copy the child pointers to the new one */ + node = *let_ptr; + for (i = 0; i < TOMMY_TRIE_INPLACE_TREE_MAX; ++i) + node->map[i] = remove->map[i]; + + return remove; + } + + /* find a leaf */ + leaf_let_ptr = 0; + leaf = remove; + + /* search backward, statistically we have more zeros than ones */ + i = TOMMY_TRIE_INPLACE_TREE_MAX - 1; + while (i >= 0) { + if (leaf->map[i]) { + leaf_let_ptr = &leaf->map[i]; + leaf = *leaf_let_ptr; + i = TOMMY_TRIE_INPLACE_TREE_MAX - 1; + continue; + } + --i; + } + + /* if it's itself a leaf */ + if (!leaf_let_ptr) + return remove; + + /* remove the leaf */ + *leaf_let_ptr = 0; + + /* copy the child pointers */ + for (i = 0; i < TOMMY_TRIE_INPLACE_TREE_MAX; ++i) + leaf->map[i] = remove->map[i]; + + /* put it in place */ + *let_ptr = leaf; + + return remove; +} + +void* tommy_trie_inplace_remove(tommy_trie_inplace* trie_inplace, tommy_key_t key) +{ + tommy_trie_inplace_node* ret; + tommy_trie_inplace_node** let_ptr; + + let_ptr = &trie_inplace->bucket[key >> TOMMY_TRIE_INPLACE_BUCKET_SHIFT]; + + ret = trie_inplace_bucket_remove(TOMMY_TRIE_INPLACE_BUCKET_SHIFT, let_ptr, 0, key); + + if (!ret) + return 0; + + --trie_inplace->count; + + return ret->data; +} + +void* tommy_trie_inplace_remove_existing(tommy_trie_inplace* trie_inplace, tommy_trie_inplace_node* node) +{ + tommy_trie_inplace_node* ret; + tommy_key_t key = node->key; + tommy_trie_inplace_node** let_ptr; + + let_ptr = &trie_inplace->bucket[key >> TOMMY_TRIE_INPLACE_BUCKET_SHIFT]; + + ret = trie_inplace_bucket_remove(TOMMY_TRIE_INPLACE_BUCKET_SHIFT, let_ptr, node, key); + + /* the element removed must match the one passed */ + assert(ret == node); + + --trie_inplace->count; + + return ret->data; +} + +tommy_trie_inplace_node* tommy_trie_inplace_bucket(tommy_trie_inplace* trie_inplace, tommy_key_t key) +{ + tommy_trie_inplace_node* node; + tommy_uint_t shift; + + node = trie_inplace->bucket[key >> TOMMY_TRIE_INPLACE_BUCKET_SHIFT]; + shift = TOMMY_TRIE_INPLACE_BUCKET_SHIFT; + + while (node && node->key != key) { + node = node->map[(key >> shift) & TOMMY_TRIE_INPLACE_TREE_MASK]; + shift -= TOMMY_TRIE_INPLACE_TREE_BIT; + } + + return node; +} + +tommy_size_t tommy_trie_inplace_memory_usage(tommy_trie_inplace* trie_inplace) +{ + return tommy_trie_inplace_count(trie_inplace) * (tommy_size_t)sizeof(tommy_trie_inplace_node); +} + diff --git a/third-party/tommyds/tommytrieinp.h b/third-party/tommyds/tommytrieinp.h new file mode 100644 index 0000000..9448a50 --- /dev/null +++ b/third-party/tommyds/tommytrieinp.h @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Inplace trie. + * + * This trie is a inplace implementation not needing any external allocation. + * + * Elements are not stored in order, like ::tommy_trie, because some elements + * should be used to represent the inner nodes in the trie. + * + * You can control the number of branches of each node using the ::TOMMY_TRIE_INPLACE_TREE_MAX define. + * More branches imply more speed, but a bigger memory occupation. + * + * Compared to ::tommy_trie you should use a lower number of branches to limit the unused memory + * occupation of the leaf nodes. This imply a lower speed, but without the need of an external allocator. + * + * To initialize the trie you have to call tommy_trie_inplace_init(). + * + * \code + * tommy_trie_inplace trie_inplace; + * + * tommy_trie_inplace_init(&trie_inplace); + * \endcode + * + * To insert elements in the trie you have to call tommy_trie_inplace_insert() for + * each element. + * In the insertion call you have to specify the address of the node, the + * address of the object, and the key value to use. + * The address of the object is used to initialize the tommy_node::data field + * of the node, and the key to initialize the tommy_node::key field. + * + * \code + * struct object { + * int value; + * // other fields + * tommy_node node; + * }; + * + * struct object* obj = malloc(sizeof(struct object)); // creates the object + * + * obj->value = ...; // initializes the object + * + * tommy_trie_inplace_insert(&trie_inplace, &obj->node, obj, obj->value); // inserts the object + * \endcode + * + * To find and element in the trie you have to call tommy_trie_inplace_search() providing + * the key to search. + * + * \code + * int value_to_find = 1; + * struct object* obj = tommy_trie_inplace_search(&trie_inplace, value_to_find); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + * To iterate over all the elements in the trie with the same key, you have to + * use tommy_trie_inplace_bucket() and follow the tommy_node::next pointer until NULL. + * + * \code + * int value_to_find = 1; + * tommy_node* i = tommy_trie_inplace_bucket(&trie_inplace, value_to_find); + * while (i) { + * struct object* obj = i->data; // gets the object pointer + * + * printf("%d\n", obj->value); // process the object + * + * i = i->next; // goes to the next element + * } + * \endcode + * + * To remove an element from the trie you have to call tommy_trie_inplace_remove() + * providing the key to search and remove. + * + * \code + * struct object* obj = tommy_trie_inplace_remove(&trie_inplace, value_to_remove); + * if (obj) { + * free(obj); // frees the object allocated memory + * } + * \endcode + * + * To destroy the trie you have only to remove all the elements, as the trie is + * completely inplace and it doesn't allocate memory. + * + * Note that you cannot iterate over all the elements in the trie using the + * trie itself. You have to insert all the elements also in a ::tommy_list, + * and use the list to iterate. See the \ref multiindex example for more detail. + */ + +#ifndef __TOMMYTRIEINP_H +#define __TOMMYTRIEINP_H + +#include "tommytypes.h" + +/******************************************************************************/ +/* trie_inplace */ + +/** + * Number of branches on each node. It must be a power of 2. + * Suggested values are 2, 4 and 8. + * Any node, including leafs, contains a pointer to each branch. + */ +#define TOMMY_TRIE_INPLACE_TREE_MAX 4 + +/** \internal + * Number of bits for each branch. + */ +#define TOMMY_TRIE_INPLACE_TREE_BIT TOMMY_ILOG2(TOMMY_TRIE_INPLACE_TREE_MAX) + +/** \internal + * Number of bits of the first level. + */ +#define TOMMY_TRIE_INPLACE_BUCKET_BIT ((TOMMY_KEY_BIT % TOMMY_TRIE_INPLACE_TREE_BIT) + 3 * TOMMY_TRIE_INPLACE_TREE_BIT) + +/** \internal + * Number of branches of the first level. + * It's like a inner branch, but bigger to get any remainder bits. + */ +#define TOMMY_TRIE_INPLACE_BUCKET_MAX (1 << TOMMY_TRIE_INPLACE_BUCKET_BIT) + +/** + * Trie node. + * This is the node that you have to include inside your objects. + */ +typedef struct tommy_trie_inplace_node_struct { + struct tommy_trie_inplace_node_struct* next; /**< Next element. 0 if it's the last. */ + struct tommy_trie_inplace_node_struct* prev; /**< Circular previous element. */ + void* data; /**< Pointer to the data. */ + tommy_key_t key; /**< Used to store the key or the hash. */ + struct tommy_trie_inplace_node_struct* map[TOMMY_TRIE_INPLACE_TREE_MAX]; /** Branches of the node. */ +} tommy_trie_inplace_node; + +/** + * Trie container type. + * \note Don't use internal fields directly, but access the container only using functions. + */ +typedef struct tommy_trie_inplace_struct { + tommy_trie_inplace_node* bucket[TOMMY_TRIE_INPLACE_BUCKET_MAX]; /**< First tree level. */ + tommy_count_t count; /**< Number of elements. */ +} tommy_trie_inplace; + +/** + * Initializes the trie. + * + * The tries is completely inplace, and it doesn't need to be deinitialized. + */ +void tommy_trie_inplace_init(tommy_trie_inplace* trie_inplace); + +/** + * Inserts an element in the trie. + */ +void tommy_trie_inplace_insert(tommy_trie_inplace* trie_inplace, tommy_trie_inplace_node* node, void* data, tommy_key_t key); + +/** + * Searches and removes the first element with the specified key. + * If the element is not found, 0 is returned. + * If more equal elements are present, the first one is removed. + * This operation is faster than calling tommy_trie_inplace_bucket() and tommy_trie_inplace_remove_existing() separately. + * \param key Key of the element to find and remove. + * \return The removed element, or 0 if not found. + */ +void* tommy_trie_inplace_remove(tommy_trie_inplace* trie_inplace, tommy_key_t key); + +/** + * Gets the bucket of the specified key. + * The bucket is guaranteed to contain ALL and ONLY the elements with the specified key. + * You can access elements in the bucket following the ::next pointer until 0. + * \param key Key of the element to find. + * \return The head of the bucket, or 0 if empty. + */ +tommy_trie_inplace_node* tommy_trie_inplace_bucket(tommy_trie_inplace* trie_inplace, tommy_key_t key); + +/** + * Searches an element in the trie. + * You have to provide the key of the element you want to find. + * If more elements with the same key are present, the first one is returned. + * \param key Key of the element to find. + * \return The first element found, or 0 if none. + */ +tommy_inline void* tommy_trie_inplace_search(tommy_trie_inplace* trie_inplace, tommy_key_t key) +{ + tommy_trie_inplace_node* i = tommy_trie_inplace_bucket(trie_inplace, key); + + if (!i) + return 0; + + return i->data; +} + +/** + * Removes an element from the trie. + * You must already have the address of the element to remove. + * \return The tommy_node::data field of the node removed. + */ +void* tommy_trie_inplace_remove_existing(tommy_trie_inplace* trie_inplace, tommy_trie_inplace_node* node); + +/** + * Gets the number of elements. + */ +tommy_inline tommy_count_t tommy_trie_inplace_count(tommy_trie_inplace* trie_inplace) +{ + return trie_inplace->count; +} + +/** + * Gets the size of allocated memory. + * It includes the size of the ::tommy_inplace_node of the stored elements. + */ +tommy_size_t tommy_trie_inplace_memory_usage(tommy_trie_inplace* trie_inplace); + +#endif + diff --git a/third-party/tommyds/tommytypes.h b/third-party/tommyds/tommytypes.h new file mode 100644 index 0000000..a52d910 --- /dev/null +++ b/third-party/tommyds/tommytypes.h @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2010, Andrea Mazzoleni. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * Generic types. + */ + +#ifndef __TOMMYTYPES_H +#define __TOMMYTYPES_H + +/******************************************************************************/ +/* types */ + +#include <stddef.h> + +#if defined(_MSC_VER) +typedef unsigned tommy_uint32_t; /**< Generic uint32_t type. */ +typedef unsigned _int64 tommy_uint64_t; /**< Generic uint64_t type. */ +typedef size_t tommy_uintptr_t; /**< Generic uintptr_t type. */ +#else +#include <stdint.h> +typedef uint32_t tommy_uint32_t; /**< Generic uint32_t type. */ +typedef uint64_t tommy_uint64_t; /**< Generic uint64_t type. */ +typedef uintptr_t tommy_uintptr_t; /**< Generic uintptr_t type. */ +#endif +typedef size_t tommy_size_t; /**< Generic size_t type. */ +typedef ptrdiff_t tommy_ptrdiff_t; /**< Generic ptrdiff_t type. */ +typedef int tommy_bool_t; /**< Generic boolean type. */ + +/** + * Generic unsigned integer type. + * + * It has no specific size, as is used to store only small values. + * To make the code more efficient, a full 32 bit integer is used. + */ +typedef tommy_uint32_t tommy_uint_t; + +/** + * Generic unsigned integer for counting objects. + * + * TommyDS doesn't support more than 2^32-1 objects. + */ +typedef tommy_uint32_t tommy_count_t; + +/** \internal + * Type cast required for the C++ compilation. + * When compiling in C++ we cannot convert a void* pointer to another pointer. + * In such case we need an explicit cast. + */ +#ifdef __cplusplus +#define tommy_cast(type, value) static_cast<type>(value) +#else +#define tommy_cast(type, value) (value) +#endif + +/******************************************************************************/ +/* heap */ + +/* by default uses malloc/calloc/realloc/free */ + +/** + * Generic malloc(), calloc(), realloc() and free() functions. + * Redefine them to what you need. By default they map to the C malloc(), calloc(), realloc() and free(). + */ +#if !defined(tommy_malloc) || !defined(tommy_calloc) || !defined(tommy_realloc) || !defined(tommy_free) +#include "rtrlib/lib/alloc_utils_private.h" +#endif +#if !defined(tommy_malloc) +#define tommy_malloc lrtr_malloc +#endif +#if !defined(tommy_calloc) +#define tommy_calloc lrtr_calloc +#endif +#if !defined(tommy_realloc) +#define tommy_realloc lrtr_realloc +#endif +#if !defined(tommy_free) +#define tommy_free lrtr_free +#endif + +/******************************************************************************/ +/* modificators */ + +/** \internal + * Definition of the inline keyword if available. + */ +#if !defined(tommy_inline) +#if defined(_MSC_VER) || defined(__GNUC__) +#define tommy_inline static __inline +#else +#define tommy_inline static +#endif +#endif + +/** \internal + * Definition of the restrict keyword if available. + */ +#if !defined(tommy_restrict) +#if __STDC_VERSION__ >= 199901L +#define tommy_restrict restrict +#elif defined(_MSC_VER) || defined(__GNUC__) +#define tommy_restrict __restrict +#else +#define tommy_restrict +#endif +#endif + +/** \internal + * Hints the compiler that a condition is likely true. + */ +#if !defined(tommy_likely) +#if defined(__GNUC__) +#define tommy_likely(x) __builtin_expect(!!(x), 1) +#else +#define tommy_likely(x) (x) +#endif +#endif + +/** \internal + * Hints the compiler that a condition is likely false. + */ +#if !defined(tommy_unlikely) +#if defined(__GNUC__) +#define tommy_unlikely(x) __builtin_expect(!!(x), 0) +#else +#define tommy_unlikely(x) (x) +#endif +#endif + +/******************************************************************************/ +/* key */ + +/** + * Key type used in indexed data structures to store the key or the hash value. + */ +typedef tommy_uint32_t tommy_key_t; + +/** + * Bits into the ::tommy_key_t type. + */ +#define TOMMY_KEY_BIT (sizeof(tommy_key_t) * 8) + +/******************************************************************************/ +/* node */ + +/** + * Data structure node. + * This node type is shared between all the data structures and used to store some + * info directly into the objects you want to store. + * + * A typical declaration is: + * \code + * struct object { + * tommy_node node; + * // other fields + * }; + * \endcode + */ +typedef struct tommy_node_struct { + /** + * Next node. + * The tail node has it at 0, like a 0 terminated list. + */ + struct tommy_node_struct* next; + + /** + * Previous node. + * The head node points to the tail node, like a circular list. + */ + struct tommy_node_struct* prev; + + /** + * Pointer to the object containing the node. + * This field is initialized when inserting nodes into a data structure. + */ + void* data; + + /** + * Key used to store the node. + * With hashtables this field is used to store the hash value. + * With lists this field is not used. + */ + tommy_key_t key; +} tommy_node; + +/******************************************************************************/ +/* compare */ + +/** + * Compare function for elements. + * \param obj_a Pointer to the first object to compare. + * \param obj_b Pointer to the second object to compare. + * \return <0 if the first element is less than the second, ==0 equal, >0 if greather. + * + * This function is like the C strcmp(). + * + * \code + * struct object { + * tommy_node node; + * int value; + * }; + * + * int compare(const void* obj_a, const void* obj_b) + * { + * if (((const struct object*)obj_a)->value < ((const struct object*)obj_b)->value) + * return -1; + * if (((const struct object*)obj_a)->value > ((const struct object*)obj_b)->value) + * return 1; + * return 0; + * } + * + * tommy_list_sort(&list, compare); + * \endcode + * + */ +typedef int tommy_compare_func(const void* obj_a, const void* obj_b); + +/** + * Search function for elements. + * \param arg Pointer to the value to search as passed at the search function. + * \param obj Pointer to the object to compare to. + * \return ==0 if the value matches the element. !=0 if different. + * + * The first argument is a pointer to the value to search exactly + * as it's passed at the search function called. + * The second argument is a pointer to the object inside the hashtable to compare. + * + * The return value has to be 0 if the values are equal. != 0 if they are different. + * + * \code + * struct object { + * tommy_node node; + * int value; + * }; + * + * int compare(const void* arg, const void* obj) + * { + * const int* value_to_find = arg; + * const struct object* object_to_compare = obj; + * + * return *value_to_find != object_to_compare->value; + * } + * + * int value_to_find = 1; + * struct object* obj = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find)); + * if (!obj) { + * // not found + * } else { + * // found + * } + * \endcode + * + */ +typedef int tommy_search_func(const void* arg, const void* obj); + +/** + * Foreach function. + * \param obj Pointer to the object to iterate. + * + * A typical example is to use free() to deallocate all the objects in a list. + * \code + * tommy_list_foreach(&list, (tommy_foreach_func*)free); + * \endcode + */ +typedef void tommy_foreach_func(void* obj); + +/** + * Foreach function with an argument. + * \param arg Pointer to a generic argument. + * \param obj Pointer to the object to iterate. + */ +typedef void tommy_foreach_arg_func(void* arg, void* obj); + +/******************************************************************************/ +/* bit hacks */ + +#if defined(_MSC_VER) && !defined(__cplusplus) +#include <intrin.h> +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) +#endif + +/** \internal + * Integer log2 for constants. + * You can use it only for exact power of 2 up to 256. + */ +#define TOMMY_ILOG2(value) ((value) == 256 ? 8 : (value) == 128 ? 7 : (value) == 64 ? 6 : (value) == 32 ? 5 : (value) == 16 ? 4 : (value) == 8 ? 3 : (value) == 4 ? 2 : (value) == 2 ? 1 : 0) + +/** + * Bit scan reverse or integer log2. + * Return the bit index of the most significant 1 bit. + * + * If no bit is set, the result is undefined. + * To force a return 0 in this case, you can use tommy_ilog2_u32(value | 1). + * + * Other interesting ways for bitscan are at: + * + * Bit Twiddling Hacks + * http://graphics.stanford.edu/~seander/bithacks.html + * + * Chess Programming BitScan + * http://chessprogramming.wikispaces.com/BitScan + * + * \param value Value to scan. 0 is not allowed. + * \return The index of the most significant bit set. + */ +tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value) +{ +#if defined(_MSC_VER) + unsigned long count; + _BitScanReverse(&count, value); + return count; +#elif defined(__GNUC__) + /* + * GCC implements __builtin_clz(x) as "__builtin_clz(x) = bsr(x) ^ 31" + * + * Where "x ^ 31 = 31 - x", but gcc does not optimize "31 - __builtin_clz(x)" to bsr(x), + * but generates 31 - (bsr(x) xor 31). + * + * So we write "__builtin_clz(x) ^ 31" instead of "31 - __builtin_clz(x)", + * to allow the double xor to be optimized out. + */ + return __builtin_clz(value) ^ 31; +#else + /* Find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */ + /* from http://graphics.stanford.edu/~seander/bithacks.html */ + static unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + + return TOMMY_DE_BRUIJN_INDEX_ILOG2[(tommy_uint32_t)(value * 0x07C4ACDDU) >> 27]; +#endif +} + +/** + * Bit scan forward or trailing zero count. + * Return the bit index of the least significant 1 bit. + * + * If no bit is set, the result is undefined. + * \param value Value to scan. 0 is not allowed. + * \return The index of the least significant bit set. + */ +tommy_inline tommy_uint_t tommy_ctz_u32(tommy_uint32_t value) +{ +#if defined(_MSC_VER) + unsigned long count; + _BitScanForward(&count, value); + return count; +#elif defined(__GNUC__) + return __builtin_ctz(value); +#else + /* Count the consecutive zero bits (trailing) on the right with multiply and lookup */ + /* from http://graphics.stanford.edu/~seander/bithacks.html */ + static const unsigned char TOMMY_DE_BRUIJN_INDEX_CTZ[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + + return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & - value) * 0x077CB531U)) >> 27]; +#endif +} + +/** + * Rounds up to the next power of 2. + * For the value 0, the result is undefined. + * \return The smallest power of 2 not less than the specified value. + */ +tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value) +{ + /* Round up to the next highest power of 2 */ + /* from http://graphics.stanford.edu/~seander/bithacks.html */ + + --value; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + ++value; + + return value; +} + +/** + * Check if the specified word has a byte at 0. + * \return 0 or 1. + */ +tommy_inline int tommy_haszero_u32(tommy_uint32_t value) +{ + return ((value - 0x01010101) & ~value & 0x80808080) != 0; +} +#endif + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..e101b49 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,37 @@ +add_executable(rtrclient rtrclient.c ${mustach} ${tommyds}) +set_source_files_properties("${mustach}" PROPERTIES COMPILE_FLAGS "-DNO_EXTENSION_FOR_MUSTACH -DNO_OPEN_MEMSTREAM") +set_source_files_properties("${tommyds}" PROPERTIES COMPILE_FLAGS "-Dtommy_malloc=malloc -Dtommy_calloc=calloc -Dtommy_realloc=realloc -Dtommy_free=free -include stdlib.h") +target_link_libraries(rtrclient rtrlib) +install(TARGETS rtrclient DESTINATION bin) +install(FILES "rtrclient.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + +add_executable(rpki-rov rpki-rov.c) +target_link_libraries(rpki-rov rtrlib) +install(TARGETS rpki-rov DESTINATION bin) +install(FILES "rpki-rov.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + + +set(rtrclient_pfx_templates default csv csvwithheader json) + +# Generate escaped string sequence for every template file +# This runs at configure time, changes are not picked up automatically +foreach(template_name IN LISTS rtrclient_pfx_templates) + file(READ "templates/${template_name}" template_data HEX) + string(LENGTH "${template_data}" template_length) + math(EXPR template_length "${template_length} - 1") + + set(TEMPLATES "${TEMPLATES}{ .name = \"${template_name}\", .template = \"") + + foreach(iter RANGE 0 ${template_length} 2) + string(SUBSTRING ${template_data} ${iter} 2 line) + set(TEMPLATES "${TEMPLATES}\\x${line}") + endforeach() + set(TEMPLATES "${TEMPLATES} \"},\n") + +endforeach() + +string(STRIP ${TEMPLATES} TEMPLATES) + +CONFIGURE_FILE(templates.h.cmake templates.h) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tools/rpki-rov.1 b/tools/rpki-rov.1 new file mode 100644 index 0000000..37ac947 --- /dev/null +++ b/tools/rpki-rov.1 @@ -0,0 +1,29 @@ +.\" +.\" This file is part of RTRlib. +.\" +.\" This file is subject to the terms and conditions of the MIT license. +.\" See the file LICENSE in the top level directory for more details. +.\" +.\" Website: http://rtrlib.realmv6.org/ +.\" +.TH "rpki-rov" "1" +.SH NAME +rpki-rov - rpki route origin validator +.SH SYNOPSIS +.B rpki-rov +.IR HOST +.IR PORT +.SH DESCRIPTION +\fBrpki-rov\fR is a command line interface that implements RPKI route origin validation. +It connects to the rpki server described by \fIhost\fR and \fIport\fR and can be used interactively or via pipes. +The format of the input parameter is always "\fIIP PREFIXLENGTH ASN\fR" followed by a newline. +.SH EXAMPLES +Pipe into stdin +.PP +.nf +.RS +echo "93.175.146.0 24 12654" | rpki-rov rpki.example.com 8282 +.RE +.fi +.SH BUGS +Does currently not support ssh-based rpki server. diff --git a/tools/rpki-rov.c b/tools/rpki-rov.c new file mode 100644 index 0000000..4089a88 --- /dev/null +++ b/tools/rpki-rov.c @@ -0,0 +1,231 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "rtrlib/rtrlib.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +const int connection_timeout = 20; +enum rtr_mgr_status connection_status = -1; + +static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), + enum rtr_mgr_status status, + const struct rtr_socket *socket __attribute__((unused)), + void *data __attribute__((unused))) +{ + if (status == RTR_MGR_ERROR) + connection_status = status; +} + +static int connection_error(enum rtr_mgr_status status) +{ + if (status == RTR_MGR_ERROR) { + /* + * Wait for input before printing error to avoid "broken pipe" error + * while communicating with the Python program. + */ + char input[256]; + + if (fgets(input, 256, stdin)) + ; + printf("error\n"); + fflush(stdout); + return 1; + } + return 0; +} + +static int str_to_int(const char *str, int *value) +{ + errno = 0; + int tmp = strtol(str, NULL, 10); + + if (errno != 0) + return 1; + + *value = tmp; + return 0; +} + +int main(int argc, char *argv[]) +{ + /* check arguments, need hostname/IP and port of cache-server */ + if (argc < 3) { + printf("Usage: %s [host] [port]\n", argv[0]); + return EXIT_FAILURE; + } + + struct tr_socket tr_tcp; + struct tr_tcp_config tcp_config = {argv[1], argv[2], NULL, NULL, NULL, 0}; + struct rtr_socket rtr_tcp; + struct rtr_mgr_config *conf; + struct rtr_mgr_group groups[1]; + + /* init a TCP transport and create rtr socket */ + tr_tcp_init(&tcp_config, &tr_tcp); + rtr_tcp.tr_socket = &tr_tcp; + + /* create a rtr_mgr_group array with 1 element */ + groups[0].sockets = malloc(1 * sizeof(struct rtr_socket *)); + groups[0].sockets_len = 1; + groups[0].sockets[0] = &rtr_tcp; + groups[0].preference = 1; + + if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL) < 0) + return EXIT_FAILURE; + + rtr_mgr_start(conf); + + char input[256]; + int sleep_counter = 0; + + /* wait till at least one rtr_mgr_group is synchronized with server */ + while (!rtr_mgr_conf_in_sync(conf)) { + if (connection_error(connection_status)) + return EXIT_FAILURE; + + sleep(1); + sleep_counter++; + if (sleep_counter >= connection_timeout) { + /* + * Wait for input before printing "timeout", + * to avoid "broken pipee error while communicating + * with the Python program + */ + if (fgets(input, 256, stdin)) + ; + printf("timeout\n"); + fflush(stdout); + return EXIT_FAILURE; + } + } + + int counter; + /* loop for input */ + while (1) { + int input_len; + int spaces; + + /* recheck connection, exit on failure */ + if (connection_error(connection_status)) + return EXIT_FAILURE; + + /* try reading from stdin, exit on failure */ + if (!fgets(input, 256, stdin)) { + printf("input error\n"); + return EXIT_FAILURE; + } + + /* remove newline, if present */ + input_len = strlen(input) - 1; + if (input[input_len] == '\n') + input[input_len] = '\0'; + + /* check if there are exactly 3 arguments */ + spaces = 0; + for (counter = 0; counter < input_len; counter++) { + if (input[counter] == ' ' && input[counter + 1] != ' ' && input[counter + 1] != '\0' && + counter != 0) + spaces++; + } + + /* check input matching pattern */ + if (spaces != 2) { + printf("Arguments required: IP Mask ASN\n"); + fflush(stdout); + continue; + } + + char delims[] = " "; + char *input_tok = NULL; + + input_tok = strtok(input, delims); + struct lrtr_ip_addr pref; + char ip[INET6_ADDRSTRLEN]; + + if (strlen(input_tok) > sizeof(ip) - 1) { + fprintf(stderr, "Error: Invalid ip addr\n"); + continue; + } + + memset(ip, 0, sizeof(ip)); + strncpy(ip, input_tok, sizeof(ip) - 1); + + if (lrtr_ip_str_to_addr(ip, &pref) != 0) { + fprintf(stderr, "Error: Invalid ip addr\n"); + continue; + } + + input_tok = strtok(NULL, delims); + int mask; + + if (str_to_int(input_tok, &mask)) { + fprintf(stderr, "Error: Invalid mask\n"); + continue; + } + + input_tok = strtok(NULL, delims); + int asn; + + if (str_to_int(input_tok, &asn)) { + fprintf(stderr, "Error: Invalid asn\n"); + continue; + } + + enum pfxv_state result; + struct pfx_record *reason = NULL; + unsigned int reason_len = 0; + + /* do validation */ + pfx_table_validate_r(groups[0].sockets[0]->pfx_table, &reason, &reason_len, asn, &pref, mask, &result); + + int validity_code = -1; + /* translate validation result */ + if (result == BGP_PFXV_STATE_VALID) + validity_code = 0; + else if (result == BGP_PFXV_STATE_NOT_FOUND) + validity_code = 1; + else if (result == BGP_PFXV_STATE_INVALID) + validity_code = 2; + + /* IP Mask BGP-ASN| */ + printf("%s %d %d|", ip, mask, asn); + + /* ROA-ASN IP MaskMin MaskMax, ... */ + if (reason && (reason_len > 0)) { + unsigned int i; + + for (i = 0; i < reason_len; i++) { + char tmp[100]; + + lrtr_ip_addr_to_str(&reason[i].prefix, tmp, sizeof(tmp)); + printf("%u %s %u %u", reason[i].asn, tmp, reason[i].min_len, reason[i].max_len); + if ((i + 1) < reason_len) + printf(","); + } + } + + /* |validity_code */ + printf("|%d", validity_code); + + printf("\n"); + fflush(stdout); + } + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + free(groups[0].sockets); + + return EXIT_SUCCESS; +} diff --git a/tools/rtrclient.1 b/tools/rtrclient.1 new file mode 100644 index 0000000..f9123a4 --- /dev/null +++ b/tools/rtrclient.1 @@ -0,0 +1,111 @@ +.\" +.\" This file is part of RTRlib. +.\" +.\" This file is subject to the terms and conditions of the MIT license. +.\" See the file LICENSE in the top level directory for more details. +.\" +.\" Website: http://rtrlib.realmv6.org/ +.\" +.TH "rtrclient" "1" +.SH NAME +rtrclient \- rtr rpki client +.SH SYNOPSIS +.B rtrclient +[\fB\-kph\fR] +.I SOCKETS\fR... +.SH SOCKETS +.B tcp +[\fB\-kpb \fIbindaddr\fR] +.IR HOST +.IR PORT +.br +.B ssh +[\fB\-kpb \fIbindaddr\fR] +.IR HOST +.IR PORT +.IR USERNAME +(\fIPRIVATE_KEY\fR|\fIPASSWORD\fR) +[\fIHOST_KEY\fR] +.SH DESCRIPTION +\fBrtrclient\fR connects to an RPKI/RTR cache server and prints prefix, origin AS, and router key updates. +\fBrtrclient\fR can use plain tcp or ssh transport to connect to an RPKI/RTR cache server. +The amount is not limited and different transport types can be mixed arbitrarily. +.LP +For \fBtcp\fR you must specify the \fIHOST\fR and \fIPORT\fR. +.LP +For \fBssh\fR you must specify the \fIHOST\fR, \fIPORT\fR, \fIUSERNAME\fR and a file containing the \fIPRIVATE_KEY\fR or a \fIPASSWORD\fR. +By default the rtrclient will try to guess which of the two was entered. If you want to explicitly specify this see \fB-w\fR and \fB-s\fR. +You may specify a file containing a list of \fIHOST_KEY\fRs, in the well known +.B SSH_KNOWN_HOSTS +file format. See \fIsshd(8)\fR for details. +.SH OPTIONS +\fB-b \fIbindaddr\fR +.RS 4 +Set explicit bind address +.RE +.B -h +.RS 4 +Print help message +.RE +\fB-k\fR +.RS 4 +Print information about router key updates +.RE +\fB-p\fR +.RS 4 +Print information about prefix and origin AS updates +.RE +\fB-s\fR +.RS 4 +Print information about connection status updates +.RE +\fB-e\fR +.RS 4 +Export ROAs after completing synchronisation and exit +.RE +\fB-t\fR +.RS 4 +Select template for pfx export. May be a build in template (see \fB-l\fR) or a file path to a custom template (see \fBTEMPLATES\fR) +.RE +\fB-l\fR +.RS 4 +Print available templates and exit. Prints specified templated, when used with -t. +.RE +\fB-o\fR +.RS 4 +Output file for export +.RE +\fB-w\fR +.RS 4 +force ssh authentication information to be interpreted as a password +.RE +\fB-s\fR +.RS 4 +force ssh authentication information to be interpreted as a private key +.SH TEMPLATES +Templates can be used to export ROA information in a custom format. They are written in the \fBmustache\fR(\fIhttps://mustache.github.io/\fR) templating language. + +A template should contain a section called \fBroas\fR which may contain the variables \fBprefix\fR, \fBlength\fR, \fBmaxlen\fR and \fBorigin\fR. +The content of this section is expanded for every entry in the ROA table. The special variable \fBlast\fR is true for the last entry of the prefix table. See the json template for a usage example. +.SH EXAMPLES +Print prefix and origin AS updates from a tcp based server +.PP +.nf +.RS +rtrclient tcp -k rpki.example.com 323 +.RE +.fi +.PP +Print prefix and router key updates from a ssh based server +.PP +.nf +.RS +rtrclient ssh -k -p rpki.example.com 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts +.RE +.fi +.PP +Use multiple rtr server, print prefix updates for some +.PP +.nf +.RS +rtrclient tcp -p rpki.example.com 323 tcp rpki2.example.com 323 ssh -p rpki.example.com 22 rtr-ssh ~/.ssh/id_rsa diff --git a/tools/rtrclient.c b/tools/rtrclient.c new file mode 100644 index 0000000..6282fac --- /dev/null +++ b/tools/rtrclient.c @@ -0,0 +1,1013 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + +#include "templates.h" + +#include "rtrlib/rtrlib.h" + +#include "third-party/mustach.h" +#include "third-party/tommyds/tommyarray.h" +#include "third-party/tommyds/tommyhashlin.h" + +#include <arpa/inet.h> +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#ifdef RTRLIB_HAVE_LIBSSH +// constants for utf8 validation +static const uint8_t UTF8_ONE_BYTE_PREFIX = 0b00000000; +static const uint8_t UTF8_ONE_BYTE_MASK = 0b10000000; +static const uint8_t UTF8_TWO_BYTE_PREFIX = 0b11000000; +static const uint8_t UTF8_TWO_BYTE_MASK = 0b11100000; +static const uint8_t UTF8_THREE_BYTE_PREFIX = 0b11100000; +static const uint8_t UTF8_THREE_BYTE_MASK = 0b11110000; +static const uint8_t UTF8_FOUR_BYTE_PREFIX = 0b11110000; +static const uint8_t UTF8_FOUR_BYTE_MASK = 0b11111000; +static const uint8_t UTF8_SUBSEQUENT_BYTE_PREFIX = 0b10000000; +static const uint8_t UTF8_SUBSEQUENT_BYTE_MASK = 0b11000000; +#endif + +__attribute__((format(printf, 1, 2), noreturn)) static void print_error_exit(const char *fmt, ...); + +static bool is_readable_file(const char *str); + +enum socket_type { + SOCKET_TYPE_TCP, +#ifdef RTRLIB_HAVE_LIBSSH + SOCKET_TYPE_SSH, +#endif +}; + +struct socket_config { + struct rtr_socket socket; + struct tr_socket tr_socket; + enum socket_type type; + bool print_pfx_updates; + bool print_spki_updates; + char *bindaddr; + char *host; + char *port; +#ifdef RTRLIB_HAVE_LIBSSH + char *ssh_username; + char *ssh_private_key; + char *ssh_host_key; + char *ssh_password; + bool force_password; + bool force_key; +#endif +}; + +/* activate update callback */ +bool activate_pfx_update_cb = false; +bool activate_spki_update_cb = false; + +/* print all updates regardless of socket configuration */ +bool print_all_pfx_updates = false; +bool print_all_spki_updates = false; + +bool print_status_updates = false; + +bool export_pfx = false; +char *export_file_path = NULL; +const char *template_name = NULL; + +struct socket_config **socket_config = NULL; +size_t socket_count = 0; + +pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** + * @brief Check if argument is NULL and exit if it is + */ +static void *check_alloc_ret(void *ptr) +{ + if (ptr) + return ptr; + + fprintf(stderr, "Memory allocation error\n"); + exit(-1); +} + +/** + * @brief Return value checking malloc wrapper. Exits program on error + */ +static inline void *checked_malloc(size_t size) +{ + return check_alloc_ret(malloc(size)); +} + +/** + * @brief Return value checking realloc wrapper. Exits program on error. + */ +static inline void *checked_realloc(void *ptr, size_t size) +{ + return check_alloc_ret(realloc(ptr, size)); +} + +static struct socket_config *extend_socket_config() +{ + socket_config = checked_realloc(socket_config, sizeof(struct socket_config *) * (socket_count + 1)); + struct socket_config *config = socket_config[socket_count] = checked_malloc(sizeof(struct socket_config)); + + memset(config, 0, sizeof(*config)); + ++socket_count; + + return config; +} + +static const char *get_template(const char *name) +{ + const struct pfx_output_template *current = templates; + + while (current->name) { + if (strcmp(name, current->name) == 0) + return current->template; + + ++current; + } + + if (is_readable_file(name)) { + FILE *template_file = fopen(name, "r"); + + if (fseek(template_file, 0, SEEK_END) == -1) + goto read_error; + + long file_size = ftell(template_file); + + if (file_size == -1) + goto read_error; + + rewind(template_file); + + char *template = checked_malloc(file_size); + + size_t bytes_read = fread(template, 1, file_size, template_file); + + if (bytes_read != (unsigned long)file_size) + goto read_error; + + fclose(template_file); + return template; + +read_error: + fclose(template_file); + print_error_exit("Could not read template"); + } + + print_error_exit("Template \"%s\" not found", name); +} + +static void print_templates(void) +{ + const struct pfx_output_template *current = templates; + + while (current->name) { + if (template_name && strcmp(current->name, template_name) == 0) + printf("%s", current->template); + else if (!template_name) + printf("%s\n", current->name); + + ++current; + } +} + +static bool is_numeric(const char *str) +{ + for (size_t i = 0; i < strlen(str); ++i) { + if (!isdigit(str[i])) + return false; + } + return true; +} + +static bool is_valid_port_number(const char *str) +{ + if (!is_numeric(str)) + return false; + + int port = atoi(str); + + if (port < 1 || port > 65535) + return false; + + return true; +} + +static bool is_resolveable_host(const char *str) +{ + struct addrinfo hints; + struct addrinfo *result = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + int ret = getaddrinfo(str, NULL, &hints, &result); + + freeaddrinfo(result); + + if (ret != 0) + return false; + return true; +} + +static bool is_file(const char *str) +{ + struct stat stat_buf; + + if (stat(str, &stat_buf) == -1) + return false; + + return S_ISREG(stat_buf.st_mode); +} + +static bool is_readable_file(const char *str) +{ + if (access(str, R_OK) != 0) + return false; + + return is_file(str); +} + +#ifdef RTRLIB_HAVE_LIBSSH +static bool is_utf8(const char *str) +{ + size_t len = strlen(str); + size_t i = 0; + + while (i < len) { + // check if current byte is a single utf8 char + if ((str[i] & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_PREFIX) { + i += 1; + + // check if current byte is the start of a two byte utf8 char and validate subsequent bytes + } else if ((str[i] & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_PREFIX && i + 1 < len && + (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { + i += 2; + + // check if current byte is the start of a three byte utf8 char and validate subsequent bytes + } else if ((str[i] & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_PREFIX && i + 2 < len && + (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && + (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { + i += 3; + + // check if current byte is the start of a four byte utf8 char and validate subsequent bytes + } else if ((str[i] & UTF8_FOUR_BYTE_MASK) == UTF8_FOUR_BYTE_PREFIX && i + 3 < len && + (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && + (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && + (str[i + 3] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { + i += 4; + + // if none of the conditions matched. The string contains at least one byte that is not valid utf8 + } else { + return false; + } + } + return true; +} +#endif + +struct exporter_state { + bool roa_section; + tommy_count_t current_roa; + tommy_array *roas; +}; + +struct pfx_export_cb_arg { + tommy_array *array; + tommy_hashlin *hashtable; +}; + +static void pfx_export_cb(const struct pfx_record *pfx_record, void *data); + +static tommy_uint32_t hash_pfx_record(const struct pfx_record *record) +{ + struct pfx_record_packed { + uint8_t ip[16]; + uint32_t asn; + uint8_t min_len; + uint8_t max_len; + } __attribute__((packed)); + + struct pfx_record_packed packed_record; + + memset(&packed_record, 0, sizeof(packed_record)); + + if (record->prefix.ver == LRTR_IPV4) + memcpy(&packed_record.ip, &record->prefix.u.addr4, sizeof(struct lrtr_ipv4_addr)); + else + memcpy(&packed_record.ip, &record->prefix.u.addr6, sizeof(struct lrtr_ipv6_addr)); + + packed_record.asn = record->asn; + packed_record.min_len = record->min_len; + packed_record.max_len = record->max_len; + + return tommy_hash_u32(0, &packed_record, sizeof(packed_record)); +} + +static int pfx_record_cmp(const void *data1, const void *data2) +{ + const struct pfx_record *arg1 = data1; + const struct pfx_record *arg2 = data2; + + if ((arg1->asn == arg2->asn) && (arg1->max_len == arg2->max_len) && (arg1->min_len == arg2->min_len) && + lrtr_ip_addr_equal(arg1->prefix, arg2->prefix)) + return 0; + + return 1; +} + +static int template_enter(void *closure, const char *name) +{ + struct exporter_state *state = closure; + + if (strncmp("roas", name, strlen(name)) == 0) { + state->roa_section = true; + state->current_roa = 0; + + return 1; + + } else if (state->current_roa && strcmp("last", name) == 0 && + state->current_roa == tommy_array_size(state->roas) - 1) { + return 1; + } + + return 0; +} + +static int template_leave(void *closure) +{ + struct exporter_state *state = closure; + + state->roa_section = false; + return 0; +} + +static int template_next(void *closure) +{ + struct exporter_state *state = closure; + + if (++state->current_roa >= tommy_array_size(state->roas)) + return 0; + + return 1; +} + +static int template_put(void *closure, const char *name, __attribute__((unused)) int escape, FILE *file) +{ + struct exporter_state *state = closure; + + size_t namelen = strlen(name); + + if (!state->roa_section || namelen == 0) + return MUSTACH_ERROR_SYSTEM; + + struct pfx_record *pfx_record = tommy_array_get(state->roas, state->current_roa); + + if (strncmp("prefix", name, namelen) == 0) { + char prefix_str[INET6_ADDRSTRLEN]; + + lrtr_ip_addr_to_str(&pfx_record->prefix, prefix_str, sizeof(prefix_str)); + fputs(prefix_str, file); + + } else if (strncmp("length", name, namelen) == 0) { + fprintf(file, "%d", pfx_record->min_len); + + } else if (strncmp("maxlen", name, namelen) == 0) { + fprintf(file, "%d", pfx_record->max_len); + + } else if (strncmp("origin", name, namelen) == 0) { + fprintf(file, "%d", pfx_record->asn); + } + + return MUSTACH_OK; +} + +struct mustach_itf template_itf = {.start = NULL, + .put = template_put, + .enter = template_enter, + .next = template_next, + .leave = template_leave}; + +static void print_usage(char **argv) +{ + printf("Usage:\n"); + printf(" %s [-hpkels] [-o file] [-t template] <socket>...\n", argv[0]); + printf("\nSocket:\n"); + printf(" tcp [-hpkb bindaddr] <host> <port>\n"); +#ifdef RTRLIB_HAVE_LIBSSH + printf(" ssh [-hpkb bindaddr] <host> <port> <username> (<private_key> | <password>) [<host_key>]\n"); +#endif + printf("\nOptions:\n"); + printf("-b bindaddr Hostnamne or IP address to connect from\n\n"); + +#ifdef RTRLIB_HAVE_LIBSSH + printf("-w force ssh authentication information to be interpreted as a password\n"); + printf("-r force ssh authentication information to be interpreted as a private key\n\n"); +#endif + + printf("-k Print information about SPKI updates.\n"); + printf("-p Print information about PFX updates.\n"); + printf("-s Print information about connection status updates.\n\n"); + + printf("-e export pfx table and exit\n"); + printf("-o output file for export\n"); + printf("-t template used for export\n"); + printf("-l list available templates\n\n"); + + printf("-h Print this help message.\n"); + + printf("\nExamples:\n"); + printf(" %s tcp rpki-validator.realmv6.org 8283\n", argv[0]); + printf(" %s tcp -k -p rpki-validator.realmv6.org 8283\n", argv[0]); + printf(" %s tcp -k rpki-validator.realmv6.org 8283 tcp -s example.com 323\n", argv[0]); + printf(" %s -kp tcp rpki-validator.realmv6.org 8283 tcp example.com 323\n", argv[0]); +#ifdef RTRLIB_HAVE_LIBSSH + printf(" %s ssh rpki-validator.realmv6.org 22 rtr-ssh", argv[0]); + printf(" ~/.ssh/id_rsa ~/.ssh/known_hosts\n"); + printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts\n", argv[0]); + printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts", argv[0]); + printf(" ssh -k -p example.com 22 rtr-ssh ~/.ssh/id_rsa_example\n"); + printf(" %s ssh -k -p rpki-validator.realmv6.org 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts", argv[0]); + printf(" tcp -k -p example.com 323\n"); +#endif + + printf(" %s -e tcp rpki-validator.realmv6.org 8283\n", argv[0]); + printf(" %s -e -t csv -o roa.csv tcp rpki-validator.realmv6.org 8283\n", argv[0]); +} + +static void status_fp(const struct rtr_mgr_group *group __attribute__((unused)), enum rtr_mgr_status mgr_status, + const struct rtr_socket *rtr_sock, void *data __attribute__((unused))) +{ + if (print_status_updates) { + pthread_mutex_lock(&stdout_mutex); + printf("RTR-Socket changed connection status to: %s, Mgr Status: %s\n", + rtr_state_to_str(rtr_sock->state), rtr_mgr_status_to_str(mgr_status)); + pthread_mutex_unlock(&stdout_mutex); + } +} + +static void update_cb(struct pfx_table *p __attribute__((unused)), const struct pfx_record rec, const bool added) +{ + char ip[INET6_ADDRSTRLEN]; + + const struct socket_config *config = (const struct socket_config *)rec.socket; + + if (!print_all_pfx_updates && !config->print_pfx_updates) + return; + + pthread_mutex_lock(&stdout_mutex); + if (added) + printf("+ "); + else + printf("- "); + lrtr_ip_addr_to_str(&rec.prefix, ip, sizeof(ip)); + if (socket_count > 1) + printf("%s:%s %-40s %3u - %3u %10u\n", config->host, config->port, ip, rec.min_len, rec.max_len, + rec.asn); + else + printf("%-40s %3u - %3u %10u\n", ip, rec.min_len, rec.max_len, rec.asn); + + pthread_mutex_unlock(&stdout_mutex); +} + +static void update_spki(struct spki_table *s __attribute__((unused)), const struct spki_record record, const bool added) +{ + const struct socket_config *config = (const struct socket_config *)record.socket; + + if (!print_all_spki_updates && !config->print_spki_updates) + return; + + pthread_mutex_lock(&stdout_mutex); + + char c; + + if (added) + c = '+'; + else + c = '-'; + + printf("%c ", c); + printf("HOST: %s:%s\n", config->host, config->port); + printf("ASN: %u\n ", record.asn); + + int i; + int size = sizeof(record.ski); + + printf("SKI: "); + for (i = 0; i < size; i++) { + printf("%02x", record.ski[i]); + if (i < size - 1) + printf(":"); + } + printf("\n "); + + i = 0; + size = sizeof(record.spki); + printf("SPKI: "); + for (i = 0; i < size; i++) { + if ((i % 40 == 0) && (i != 0)) + printf("\n "); + + printf("%02x", record.spki[i]); + if (i < size - 1) + printf(":"); + } + printf("\n"); + + pthread_mutex_unlock(&stdout_mutex); +} + +static void parse_global_opts(int argc, char **argv) +{ + int opt; + + bool print_template = false; + + while ((opt = getopt(argc, argv, "+kphelo:t:s")) != -1) { + switch (opt) { + case 'k': + activate_spki_update_cb = true; + print_all_spki_updates = true; + break; + + case 'p': + activate_pfx_update_cb = true; + print_all_pfx_updates = true; + break; + + case 'e': + export_pfx = true; + break; + + case 'o': + if (export_file_path) + print_error_exit("output file can not be specified more than once"); + + export_file_path = optarg; + break; + + case 't': + template_name = optarg; + break; + + case 'l': + print_template = true; + break; + + case 's': + print_status_updates = true; + break; + + default: + print_usage(argv); + exit(EXIT_FAILURE); + } + } + + if (print_template) { + print_templates(); + exit(EXIT_SUCCESS); + } + + if ((export_file_path || template_name) && !export_pfx) + print_error_exit("Specifying -o or -t without -e does not make sense"); +} + +static void parse_socket_opts(int argc, char **argv, struct socket_config *config) +{ + int opt; + + while ((opt = getopt(argc, argv, "+kphwrb:")) != -1) { + switch (opt) { + case 'k': + activate_spki_update_cb = true; + config->print_spki_updates = true; + break; + + case 'p': + activate_pfx_update_cb = true; + config->print_pfx_updates = true; + break; + + case 'b': + config->bindaddr = optarg; + break; + +#ifdef RTRLIB_HAVE_LIBSSH + case 'w': + if (config->force_key) + print_error_exit("-w and -r are mutually exclusive"); + + config->force_password = true; + break; + + case 'r': + if (config->force_password) + print_error_exit("-w and -r are mutually exclusive"); + + config->force_key = true; + break; +#endif + + default: + print_usage(argv); + exit(EXIT_FAILURE); + } + } +} + +static void print_error_exit(const char *fmt, ...) +{ + va_list argptr; + + va_start(argptr, fmt); + + fprintf(stderr, "error: "); + + vfprintf(stderr, fmt, argptr); + + fprintf(stderr, "\n"); + + va_end(argptr); + + exit(EXIT_FAILURE); +} + +static int parse_cli(int argc, char **argv) +{ + enum cli_parse_state { + CLI_PARSE_STATE_BEGIN, + CLI_PARSE_STATE_GLOBAL_OPTS, + CLI_PARSE_STATE_SOCKET_BEGIN, + CLI_PARSE_STATE_SOCKET_TCP_OPTIONS, + CLI_PARSE_STATE_SOCKET_TCP_HOST, + CLI_PARSE_STATE_SOCKET_TCP_PORT, +#ifdef RTRLIB_HAVE_LIBSSH + CLI_PARSE_STATE_SOCKET_SSH_OPTIONS, + CLI_PARSE_STATE_SOCKET_SSH_HOST, + CLI_PARSE_STATE_SOCKET_SSH_PORT, + CLI_PARSE_STATE_SOCKET_SSH_USERNAME, + CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD, + CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY, +#endif + CLI_PARSE_STATE_END, + }; + + enum cli_parse_state state = CLI_PARSE_STATE_BEGIN; + + struct socket_config *current_config; + + while (true) { + switch (state) { + case CLI_PARSE_STATE_BEGIN: + optind = 1; + state = CLI_PARSE_STATE_GLOBAL_OPTS; + break; + + case CLI_PARSE_STATE_GLOBAL_OPTS: + parse_global_opts(argc, argv); + + state = CLI_PARSE_STATE_SOCKET_BEGIN; + break; + + case CLI_PARSE_STATE_SOCKET_BEGIN: + current_config = extend_socket_config(); + + if (strncasecmp(argv[optind], "tcp", strlen(argv[optind])) == 0) { + state = CLI_PARSE_STATE_SOCKET_TCP_OPTIONS; + current_config->type = SOCKET_TYPE_TCP; + + /* Check if enough arguments are available + * to configure this socket. + * This does not account for options. + */ + if ((argc - optind) < 2) + print_error_exit("Not enough arguments for tcp socket"); + + } else if (strncasecmp(argv[optind], "ssh", strlen(argv[optind])) == 0) { +#ifdef RTRLIB_HAVE_LIBSSH + state = CLI_PARSE_STATE_SOCKET_SSH_OPTIONS; + current_config->type = SOCKET_TYPE_SSH; + + /* Check if enough arguments are available + * to configure this socket. + * This does not account for options. + */ + if ((argc - optind) < 4) + print_error_exit("Not enough arguments for ssh socket"); +#else + print_error_exit("ssh support disabled at compile time\n"); +#endif + + } else { + print_error_exit("\"%s\" is not a valid socket type\n", argv[optind]); + break; + } + + ++optind; + break; + + case CLI_PARSE_STATE_SOCKET_TCP_OPTIONS: + parse_socket_opts(argc, argv, current_config); + + /* Check again if enough arguments are available, + * accounting for options + */ + if ((argc - optind) < 2) + print_error_exit("Not enough arguments for tcp socket"); + + state = CLI_PARSE_STATE_SOCKET_TCP_HOST; + break; + + case CLI_PARSE_STATE_SOCKET_TCP_HOST: + if (!is_resolveable_host(argv[optind])) + print_error_exit("cannot resolve \"%s\"\n", argv[optind]); + + current_config->host = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_TCP_PORT; + break; + + case CLI_PARSE_STATE_SOCKET_TCP_PORT: + if (!is_valid_port_number(argv[optind])) + print_error_exit("\"%s\" is not a valid port number\n", argv[optind]); + + current_config->port = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_BEGIN; + break; + +#ifdef RTRLIB_HAVE_LIBSSH + case CLI_PARSE_STATE_SOCKET_SSH_OPTIONS: + parse_socket_opts(argc, argv, current_config); + + /* Check again if enough arguments are available, + * accounting for options + */ + if ((argc - optind) < 4) + print_error_exit("Not enough arguments for ssh socket"); + + state = CLI_PARSE_STATE_SOCKET_SSH_HOST; + break; + + case CLI_PARSE_STATE_SOCKET_SSH_HOST: + if (!is_resolveable_host(argv[optind])) + print_error_exit("cannot resolve \"%s\"\n", argv[optind]); + + current_config->host = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_SSH_PORT; + break; + + case CLI_PARSE_STATE_SOCKET_SSH_PORT: + if (!is_valid_port_number(argv[optind])) + print_error_exit("\"%s\" is not a valid port number\n", argv[optind]); + + current_config->port = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_SSH_USERNAME; + break; + + case CLI_PARSE_STATE_SOCKET_SSH_USERNAME: + current_config->ssh_username = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD; + break; + + case CLI_PARSE_STATE_SOCKET_SSH_PRIVATE_KEY_PASSWORD: + if (current_config->force_key && !is_readable_file(argv[optind])) { + print_error_exit("\"%s\" is not a readable file\n", argv[optind]); + + } else if (!current_config->force_password && !current_config->force_key && + is_readable_file(argv[optind])) { + current_config->ssh_private_key = argv[optind]; + + } else if (!current_config->force_password && is_utf8(argv[optind])) { + fprintf(stderr, "\"%s\" does not seem to be a file. Trying password authentication.\n", + argv[optind]); + fprintf(stderr, "Use -r to force key authentication or -w to silence this warning"); + current_config->ssh_password = argv[optind]; + + } else if (current_config->force_password && !is_utf8(argv[optind])) { + print_error_exit("\"%s\" is not a valid utf8 string", argv[optind]); + + } else if (current_config->force_password && !current_config->force_key && + is_utf8(argv[optind])) { + current_config->ssh_password = argv[optind]; + + } else { + print_error_exit("\"%s\" is neither a readable file nor a valid utf8 string", + argv[optind]); + } + + ++optind; + state = CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY; + break; + + case CLI_PARSE_STATE_SOCKET_SSH_HOST_KEY: + if (strncasecmp(argv[optind], "tcp", strlen(argv[optind])) == 0 || + strncasecmp(argv[optind], "ssh", strlen(argv[optind])) == 0) { + state = CLI_PARSE_STATE_SOCKET_BEGIN; + break; + } + + if (!is_readable_file(argv[optind])) + print_error_exit("\"%s\" is not a readable file\n", argv[optind]); + + current_config->ssh_host_key = argv[optind++]; + + state = CLI_PARSE_STATE_SOCKET_BEGIN; + break; +#endif + + case CLI_PARSE_STATE_END: + return 0; + } + + if (optind >= argc) + state = CLI_PARSE_STATE_END; + } +} + +static void init_sockets(void) +{ + for (size_t i = 0; i < socket_count; ++i) { + struct socket_config *config = socket_config[i]; + struct tr_tcp_config tcp_config = {}; +#ifdef RTRLIB_HAVE_LIBSSH + struct tr_ssh_config ssh_config = {}; +#endif + + switch (config->type) { + case SOCKET_TYPE_TCP: + tcp_config.host = config->host; + tcp_config.port = config->port; + tcp_config.bindaddr = config->bindaddr; + + tr_tcp_init(&tcp_config, &config->tr_socket); + config->socket.tr_socket = &config->tr_socket; + break; + +#ifdef RTRLIB_HAVE_LIBSSH + case SOCKET_TYPE_SSH: + ssh_config.host = config->host; + ssh_config.port = atoi(config->port); + ssh_config.bindaddr = config->bindaddr; + ssh_config.username = config->ssh_username; + ssh_config.client_privkey_path = config->ssh_private_key; + ssh_config.server_hostkey_path = config->ssh_host_key; + ssh_config.password = config->ssh_password; + + tr_ssh_init(&ssh_config, &config->tr_socket); + config->socket.tr_socket = &config->tr_socket; + break; +#endif + } + } +} + +int main(int argc, char **argv) +{ + parse_cli(argc, argv); + + if (socket_count == 0) { + print_usage(argv); + exit(EXIT_FAILURE); + } + + init_sockets(); + + struct rtr_mgr_config *conf; + struct rtr_mgr_group groups[1]; + + groups[0].sockets_len = socket_count; + groups[0].sockets = (struct rtr_socket **)socket_config; + groups[0].preference = 1; + + spki_update_fp spki_update_fp = activate_spki_update_cb ? update_spki : NULL; + pfx_update_fp pfx_update_fp = activate_pfx_update_cb ? update_cb : NULL; + + int ret = rtr_mgr_init(&conf, groups, 1, 30, 600, 600, pfx_update_fp, spki_update_fp, status_fp, NULL); + + if (ret == RTR_ERROR) + fprintf(stderr, "Error in rtr_mgr_init!\n"); + else if (ret == RTR_INVALID_PARAM) + fprintf(stderr, "Invalid params passed to rtr_mgr_init\n"); + + if (!conf) + return EXIT_FAILURE; + + if (!export_pfx && activate_pfx_update_cb && socket_count > 1) + printf("%-40s %-40s %3s %3s %3s\n", "host", "Prefix", "Prefix Length", "", "ASN"); + else if (!export_pfx && activate_pfx_update_cb) + printf("%-40s %3s %3s %3s\n", "Prefix", "Prefix Length", "", "ASN"); + + if (export_pfx) { + const char *template; + + if (template_name) + template = get_template(template_name); + else + template = templates->template; + FILE *export_file = stdout; + + if (export_file_path) { + export_file = fopen(export_file_path, "w"); + if (!export_file) { + char *errormsg = strerror(errno); + + print_error_exit("\"%s\" could not be opened for writing: %s", optarg, errormsg); + } + } + + rtr_mgr_start(conf); + + while (!rtr_mgr_conf_in_sync(conf)) + sleep(1); + + printf("Sync done\n"); + tommy_array prefixes; + tommy_hashlin prefix_hash; + struct pfx_export_cb_arg arg = { + .array = &prefixes, + .hashtable = &prefix_hash, + }; + + tommy_array_init(&prefixes); + tommy_hashlin_init(&prefix_hash); + + pfx_table_for_each_ipv4_record(conf->pfx_table, pfx_export_cb, &arg); + pfx_table_for_each_ipv6_record(conf->pfx_table, pfx_export_cb, &arg); + + struct exporter_state state = { + .roa_section = false, + .current_roa = 0, + .roas = &prefixes, + }; + + fmustach(template, &template_itf, &state, export_file); + + tommy_hashlin_foreach(&prefix_hash, free); + tommy_hashlin_done(&prefix_hash); + tommy_array_done(&prefixes); + + if (export_file != stdout && fclose(export_file) == EOF) { + char *errormsg = strerror(errno); + + print_error_exit("\"Error during write into output file: %s\"", errormsg); + } + + } else { + rtr_mgr_start(conf); + pause(); + } + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + free(groups[0].sockets); + + return EXIT_SUCCESS; +} + +struct pfx_record_entry { + struct pfx_record record; + tommy_node hash_node; +}; + +static void pfx_export_cb(const struct pfx_record *pfx_record, void *data) +{ + struct pfx_export_cb_arg *arg = data; + tommy_array *roa_array = arg->array; + tommy_hashlin *roa_hashtable = arg->hashtable; + + tommy_hash_t record_hash = hash_pfx_record(pfx_record); + + if (tommy_hashlin_search(roa_hashtable, &pfx_record_cmp, pfx_record, record_hash) != 0) + return; + + struct pfx_record_entry *pfx_record_entry = malloc(sizeof(struct pfx_record_entry)); + + memcpy(&pfx_record_entry->record, pfx_record, sizeof(struct pfx_record)); + + tommy_hashlin_insert(roa_hashtable, &pfx_record_entry->hash_node, pfx_record_entry, record_hash); + tommy_array_insert(roa_array, pfx_record_entry); +} diff --git a/tools/templates.h.cmake b/tools/templates.h.cmake new file mode 100644 index 0000000..338c536 --- /dev/null +++ b/tools/templates.h.cmake @@ -0,0 +1,22 @@ +/* + * This file is part of RTRlib. + * + * This file is subject to the terms and conditions of the MIT license. + * See the file LICENSE in the top level directory for more details. + * + * Website: http://rtrlib.realmv6.org/ + */ + + #include <stddef.h> + + +struct pfx_output_template { + const char *name; + const char *template; +}; + + +const struct pfx_output_template templates[] = { +${TEMPLATES} +{NULL, NULL} +}; diff --git a/tools/templates/csv b/tools/templates/csv new file mode 100644 index 0000000..3b08945 --- /dev/null +++ b/tools/templates/csv @@ -0,0 +1,3 @@ +{{#roas}} +{{prefix}}, {{length}}, {{maxlen}}, {{origin}} +{{/roas}} diff --git a/tools/templates/csvwithheader b/tools/templates/csvwithheader new file mode 100644 index 0000000..01a92bc --- /dev/null +++ b/tools/templates/csvwithheader @@ -0,0 +1,4 @@ +prefix, minlen, maxlen, asn +{{#roas}} +{{prefix}}, {{length}}, {{maxlen}}, {{origin}} +{{/roas}} diff --git a/tools/templates/default b/tools/templates/default new file mode 100644 index 0000000..6fa86df --- /dev/null +++ b/tools/templates/default @@ -0,0 +1,3 @@ +{{#roas}} +{{prefix}}/{{length}}-{{maxlen}} AS {{origin}} +{{/roas}} diff --git a/tools/templates/json b/tools/templates/json new file mode 100644 index 0000000..8b7adb9 --- /dev/null +++ b/tools/templates/json @@ -0,0 +1,10 @@ +[ +{{#roas}} + { + "prefix": "{{prefix}}", + "length": "{{length}}", + "maxlen": "{{maxlen}}", + "origin": "{{origin}}" + }{{^last}},{{/last}} +{{/roas}} +] |