From 97978f8f07d64f73f84a47b038c31fa1eb99c016 Mon Sep 17 00:00:00 2001 From: Alan King Date: Fri, 15 Sep 2023 18:49:40 +0000 Subject: [PATCH] [ 7098] Add utility to generate random alphanumeric strings Adds a function which randomly generates an alphanumeric character and concatenates a string which is returned to the user. Unit tests included. --- lib/core/include/irods/irods_random.hpp | 11 ++++ lib/core/src/irods_random.cpp | 52 ++++++++++++++++++- unit_tests/CMakeLists.txt | 1 + ..._generate_random_alphanumeric_string.cmake | 10 ++++ ...st_generate_random_alphanumeric_string.cpp | 31 +++++++++++ unit_tests/unit_tests_list.json | 1 + 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 unit_tests/cmake/test_config/irods_generate_random_alphanumeric_string.cmake create mode 100644 unit_tests/src/test_generate_random_alphanumeric_string.cpp diff --git a/lib/core/include/irods/irods_random.hpp b/lib/core/include/irods/irods_random.hpp index 78cda4d4f1..226d0c02b9 100644 --- a/lib/core/include/irods/irods_random.hpp +++ b/lib/core/include/irods/irods_random.hpp @@ -1,9 +1,20 @@ #ifndef IRODS_RANDOM_HPP #define IRODS_RANDOM_HPP +#include + namespace irods { void getRandomBytes( void * buf, int bytes ); + /// \brief Generate a string of size \p _length consisting of randomly generated alphanumeric characters. + /// + /// \param[in] _length Desired length of the generated string. + /// + /// \throws irods::exception If \p _length exceeds max_size or on allocation failure. + /// + /// \since 4.3.1 + auto generate_random_alphanumeric_string(std::size_t _length) -> std::string; + template T getRandom() { diff --git a/lib/core/src/irods_random.cpp b/lib/core/src/irods_random.cpp index 663d95eab2..1b7ba3832a 100644 --- a/lib/core/src/irods_random.cpp +++ b/lib/core/src/irods_random.cpp @@ -1,7 +1,34 @@ +#include "irods/irods_random.hpp" + +#include "irods/irods_exception.hpp" +#include "irods/rodsErrorTable.h" + #include +#include #include -#include "irods/irods_random.hpp" + #include +#include +#include + +namespace +{ + auto generate_random_alphanumeric_character() -> char + { + constexpr char min_char = '0'; + constexpr char max_char = 'z'; + + static std::random_device rd; + static std::default_random_engine e{rd()}; + static std::uniform_int_distribution<> d{min_char, max_char}; + + char c{}; + while (!std::isalnum(c)) { + c = static_cast(d(e)); + } + return c; + } // generate_random_alphanumeric_character +} // anonymous namespace void irods::getRandomBytes( void * buf, int bytes ) { if ( RAND_bytes( ( unsigned char * )buf, bytes ) != 1 ) { @@ -13,3 +40,26 @@ void irods::getRandomBytes( void * buf, int bytes ) { } } } + +auto irods::generate_random_alphanumeric_string(std::size_t _length) -> std::string +{ + try { + std::string s; + s.reserve(_length); + + for (std::size_t i = 0; i < _length; ++i) { + s += generate_random_alphanumeric_character(); + } + + return s; + } + catch (const std::length_error& e) { + THROW(SYS_INVALID_INPUT_PARAM, fmt::format("{}: Invalid length. [{}]", __func__, e.what())); + } + catch (const std::exception& e) { + THROW(SYS_LIBRARY_ERROR, fmt::format("{}: Failed to generate string. [{}]", __func__, e.what())); + } + catch (...) { + THROW(SYS_UNKNOWN_ERROR, "Failed to generate string: [Unknown error]"); + } +} // generate_random_alphanumeric_string diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 188c0c209b..707df9b4de 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -46,6 +46,7 @@ set( filesystem fixed_buffer_resource fully_qualified_username + generate_random_alphanumeric_string get_delay_rule_info get_file_descriptor_info hierarchy_parser diff --git a/unit_tests/cmake/test_config/irods_generate_random_alphanumeric_string.cmake b/unit_tests/cmake/test_config/irods_generate_random_alphanumeric_string.cmake new file mode 100644 index 0000000000..8c8dbe340a --- /dev/null +++ b/unit_tests/cmake/test_config/irods_generate_random_alphanumeric_string.cmake @@ -0,0 +1,10 @@ +set(IRODS_TEST_TARGET irods_generate_random_alphanumeric_string) + +set(IRODS_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/test_generate_random_alphanumeric_string.cpp) + +set(IRODS_TEST_INCLUDE_PATH ${IRODS_EXTERNALS_FULLPATH_BOOST}/include + ${IRODS_EXTERNALS_FULLPATH_FMT}/include) + +set(IRODS_TEST_LINK_LIBRARIES irods_common + ${IRODS_EXTERNALS_FULLPATH_FMT}/lib/libfmt.so) diff --git a/unit_tests/src/test_generate_random_alphanumeric_string.cpp b/unit_tests/src/test_generate_random_alphanumeric_string.cpp new file mode 100644 index 0000000000..0266cfc791 --- /dev/null +++ b/unit_tests/src/test_generate_random_alphanumeric_string.cpp @@ -0,0 +1,31 @@ +#include + +#include "irods/irods_exception.hpp" +#include "irods/irods_random.hpp" + +#include +#include +#include + +TEST_CASE("length of 0 results in an empty string") +{ + CHECK(irods::generate_random_alphanumeric_string(0).empty()); +} + +TEST_CASE("length of 1 effectively calls generate_random_alphanumeric_character") +{ + constexpr std::size_t character_count = 1; + const auto s = irods::generate_random_alphanumeric_string(character_count); + REQUIRE(!s.empty()); + CHECK(s.size() == character_count); + CHECK(std::all_of(std::begin(s), std::end(s), [](const auto& c) { return std::isalnum(c); })); +} + +TEST_CASE("only alphanumeric characters appear in string of statistically significant length") +{ + constexpr std::size_t character_count = 1'000'000; + const auto s = irods::generate_random_alphanumeric_string(character_count); + REQUIRE(!s.empty()); + CHECK(s.size() == character_count); + CHECK(std::all_of(std::begin(s), std::end(s), [](const auto& c) { return std::isalnum(c); })); +} diff --git a/unit_tests/unit_tests_list.json b/unit_tests/unit_tests_list.json index 5355a6ba68..534e6bf086 100644 --- a/unit_tests/unit_tests_list.json +++ b/unit_tests/unit_tests_list.json @@ -16,6 +16,7 @@ "irods_filesystem", "irods_fixed_buffer_resource", "irods_fully_qualified_username", + "irods_generate_random_alphanumeric_string", "irods_get_delay_rule_info", "irods_get_file_descriptor_info", "irods_hierarchy_parser",