diff --git a/.format-ignore b/.format-ignore index dad55826..25b9752e 100644 --- a/.format-ignore +++ b/.format-ignore @@ -6,3 +6,10 @@ cpp/src/edn-cpp/example.cpp cpp/src/edn-cpp/repl.cpp cpp/src/ghc-filesystem/filesystem.hpp + +cpp/src/libb64/libb64/include/b64/cdecode.h +cpp/src/libb64/libb64/include/b64/cencode.h +cpp/src/libb64/libb64/include/b64/decode.h +cpp/src/libb64/libb64/include/b64/encode.h +cpp/src/libb64/libb64/src/cdecode.c +cpp/src/libb64/libb64/src/cencode.c diff --git a/.github/workflows/build-multiplatform.yml b/.github/workflows/build-multiplatform.yml index 669081dd..a100a25b 100644 --- a/.github/workflows/build-multiplatform.yml +++ b/.github/workflows/build-multiplatform.yml @@ -41,6 +41,7 @@ on: branches-ignore: - 'coverity_scan' pull_request: + workflow_dispatch: # Trigger workflow manually through Github API, Github CLI, or Github Web-UI jobs: @@ -49,15 +50,15 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-10.15, macos-11] + os: [macos-13, macos-14] include: - - os: macos-10.15 - DISTRO: macos_catalina - job_name: macOS Catalina 10.15 (debug) + - os: macos-13 + DISTRO: macos_ventura + job_name: macOS Ventura 13 (debug) - - os: macos-11 - DISTRO: macos_bigsur - job_name: macOS Big Sur 11 (debug) + - os: macos-14 + DISTRO: macos_sonoma + job_name: macOS Sonoma 14 (debug) name: ${{ matrix.job_name }} @@ -72,6 +73,13 @@ jobs: - name: Check out repository uses: actions/checkout@v2 + + - name: Setup Homebrew + run: echo "/opt/homebrew/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: | + brew install ccache gcc - name: Initialize ccache uses: actions/cache@v2 @@ -95,13 +103,24 @@ jobs: - name: Install ccache via homebrew run: brew install ccache + - name: Check compiler installation + run: | + echo "Checking Clang and GCC..." + which clang++ || echo "Clang++ not found" + which g++ || echo "G++ not found" + which gcc || echo "gcc not found" + which gfortran-14 || echo "gfortran-14 not found" + clang++ --version || echo "Clang++ version check failed" + g++ --version || echo "G++ version check failed" + gfortran-14 --version || echo "gfortran-14 version check failed" + - name: Build KIM API, run tests run: | export PATH="/usr/local/opt/ccache/libexec:$PATH" export CXX17=true - export CXX=g++-9 - export CC=gcc-9 - export FC=gfortran-9 + export CXX=clang++ + export CC=clang + export FC=gfortran-14 env ./scripts/ci-build-and-test @@ -335,6 +354,7 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-doxygen mingw-w64-x86_64-toolchain + mingw-w64-x86_64-gcc-fortran - name: Ensure windows ccache dir exists run: | @@ -354,6 +374,7 @@ jobs: - name: Build KIM API, run tests env: + FC: gfortran CMAKE_GENERATOR: MinGW Makefiles MAKE: mingw32-make CMAKE_MAKE_PROGRAM: mingw32-make diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d369378..4a38c38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,13 +43,22 @@ include(DefaultCompilerStandards) # if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_LOG_MAX "DEBUG") +elseif(NOT DEFINED CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") + set(_LOG_MAX "INFORMATION") + set(CMAKE_BUILD_TYPE "Release") # Default to Release, solves KIM slowdown complaints else() set(_LOG_MAX "INFORMATION") endif() + +message(STATUS "KIM-API Build Type: ${CMAKE_BUILD_TYPE}") + set_cache_with_fallback(KIM_API_LOG_MAXIMUM_LEVEL "${_LOG_MAX}" STRING "Maximum log verbosity") unset(_LOG_MAX) set_property(CACHE KIM_API_LOG_MAXIMUM_LEVEL PROPERTY STRINGS "" SILENT FATAL ERROR WARNING INFORMATION DEBUG) # +set(KIM_API_BASE64_BUFFERSIZE "16777216" CACHE STRING "base64 buffer size") +mark_as_advanced(KIM_API_BASE64_BUFFERSIZE) +# option(KIM_API_BUILD_EXAMPLES "Build example Drivers, Models, Simulator Models, and Simulators" ON) # set(KIM_API_PROJECT_NAME "kim-api" CACHE STRING "KIM API project name string") @@ -152,7 +161,7 @@ target_include_directories(kim-api PUBLIC $> $) if(NOT WIN32 OR CYGWIN) - target_link_libraries(kim-api ${CMAKE_DL_LIBS}) + target_link_libraries(kim-api PRIVATE ${CMAKE_DL_LIBS}) endif() # Add install rules for kim-api diff --git a/INSTALL b/INSTALL index 3125657f..0b7c7dd8 100644 --- a/INSTALL +++ b/INSTALL @@ -420,6 +420,9 @@ Additionally, the KIM API defines the following advanced build options. be used to build and install, on the same machine, two different copies (typically different versions) of the package. +* KIM_API_BASE64_BUFFERSIZE (="16777216") Sets the buffer size for the C++ + base64 encode/decode routines used in the kim-api. + * KIM_API_ENABLE_SANITIZE (=OFF) When ON this enables the AddressSanitizer library for detecting memory corruption bugs. diff --git a/cmake/Modules/kim-api-configuration-detailed.log.in b/cmake/Modules/kim-api-configuration-detailed.log.in index ef804e15..7cc88c8a 100644 --- a/cmake/Modules/kim-api-configuration-detailed.log.in +++ b/cmake/Modules/kim-api-configuration-detailed.log.in @@ -103,6 +103,8 @@ # # KIM_API_LOG_MAXIMUM_LEVEL: ${KIM_API_LOG_MAXIMUM_LEVEL} # +# KIM_API_BASE64_BUFFERSIZE: ${KIM_API_BASE64_BUFFERSIZE} +# # KIM_API_BUILD_EXAMPLES: ${KIM_API_BUILD_EXAMPLES} # KIM_API_ENABLE_SANITIZE: ${KIM_API_ENABLE_SANITIZE} # KIM_API_ENABLE_COVERAGE: ${KIM_API_ENABLE_COVERAGE} diff --git a/cmake/Modules/kim-api-configuration-summary.log.in b/cmake/Modules/kim-api-configuration-summary.log.in index 2244518a..95b9cc11 100644 --- a/cmake/Modules/kim-api-configuration-summary.log.in +++ b/cmake/Modules/kim-api-configuration-summary.log.in @@ -36,6 +36,8 @@ # # KIM_API_LOG_MAXIMUM_LEVEL: ${KIM_API_LOG_MAXIMUM_LEVEL} # +# KIM_API_BASE64_BUFFERSIZE: ${KIM_API_BASE64_BUFFERSIZE} +# # KIM_API_BUILD_EXAMPLES: ${KIM_API_BUILD_EXAMPLES} # KIM_API_ENABLE_SANITIZE: ${KIM_API_ENABLE_SANITIZE} # KIM_API_ENABLE_COVERAGE: ${KIM_API_ENABLE_COVERAGE} diff --git a/cmake/build-tree-config.cmake.in b/cmake/build-tree-config.cmake.in index 425aa7e1..5b31fbd3 100644 --- a/cmake/build-tree-config.cmake.in +++ b/cmake/build-tree-config.cmake.in @@ -42,6 +42,7 @@ if("${KIM-API_FIND_VERSION}" VERSION_GREATER "2.1.99") add_executable(KIM-API::portable-model-info ALIAS portable-model-info) add_executable(KIM-API::simulator-model ALIAS simulator-model) add_executable(KIM-API::collections-info ALIAS collections-info) + add_executable(KIM-API::base64-encode ALIAS base64-encode) add_executable(KIM-API::shared-library-test ALIAS shared-library-test) endif() diff --git a/cmake/items-macros.cmake.in b/cmake/items-macros.cmake.in index 78d33e9e..a5a7f7a0 100644 --- a/cmake/items-macros.cmake.in +++ b/cmake/items-macros.cmake.in @@ -494,68 +494,53 @@ macro(_set_simulator_model_template_variables) endmacro() # -# Function to create a custom command to convert a file (blob) to a c source. +# Function to create a custom command to convert a file (blob) to a cpp source. # # Convert _filein to hex then format as c source file and write to _fileout. # Assumes _filein is a relative file name in the _dirin directory. # # Sets _blob_to_c_command to string for use with add_custom_command() # -function(_add_custom_command_blob_to_c _dirin _filein _fileout) - include(FindPackageMessage) - find_program(XXD_EXECUTABLE "xxd") - if(XXD_EXECUTABLE) - find_package_message(xxd "Found xxd: (${XXD_EXECUTABLE})" "found") - - string(MAKE_C_IDENTIFIER ${_filein} _cfilein) - set(_edit_xxd_output "${CMAKE_CURRENT_BINARY_DIR}/EditXXDOutput.cmake") - if(NOT EXISTS "${_edit_xxd_output}") - file(WRITE "${_edit_xxd_output}" # use a bracket argument to avoid ugly escaping - [=[ - # This file was automatically generated by CMake; do not edit. - file(READ "${fileout}" _content) - string(REGEX REPLACE "unsigned char.*[[][]]" "unsigned char const ${varname}[]" _content "${_content}") - string(REGEX REPLACE "unsigned int.*=" "unsigned int const ${varname}_len =" _content "${_content}") - file(WRITE "${fileout}" "${_content}") - unset(_content) - ]=] - ) +function(_add_custom_command_blob_to_cpp _dirin _filein _fileout) + get_property(_IMPORTED TARGET KIM-API::base64-encode PROPERTY "IMPORTED") + if(_IMPORTED) # using install tree config + if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(_BUILD_TYPE "NOCONFIG") + else() + string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE) endif() - add_custom_command(OUTPUT ${_fileout} - COMMAND ${XXD_EXECUTABLE} -i "${_filein}" "${_fileout}" - COMMAND ${CMAKE_COMMAND} "-Dvarname=${_cfilein}" "-Dfilein=${_filein}" "-Dfileout=${_fileout}" -P "${_edit_xxd_output}" - DEPENDS "${_dirin}/${_filein}" "${_edit_xxd_output}" - WORKING_DIRECTORY "${_dirin}" - ) - unset(_cfilein) - unset(_edit_xxd_output) - else() - find_package_message(xxd "Missing xxd: Falling back to less efficient cmake implementation." "missing") - - string(MAKE_C_IDENTIFIER ${_filein} _cfilein) - set(_blob_to_source "${CMAKE_CURRENT_BINARY_DIR}/BlobToCSource.cmake") - if(NOT EXISTS "${_blob_to_source}") - file(WRITE "${_blob_to_source}" # use a bracket argument to avoid ugly escaping - [=[ - # This file was automatically generated by CMake; do not edit. - file(READ "${filein}" _content HEX) - string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," _content "${_content}") - string(REGEX REPLACE ",$" "" _content "${_content}") - file(WRITE "${fileout}" "unsigned char const ${varname}[] = { ${_content} };\n") - file(APPEND "${fileout}" "unsigned int const ${varname}_len = sizeof( ${varname} );\n") - unset(_content) - ]=] - ) + get_property(_BASE64_ENCODE TARGET KIM-API::base64-encode PROPERTY "IMPORTED_LOCATION_${_BUILD_TYPE}") + unset(_BUILD_TYPE) + if("${_BASE64_ENCODE}" STREQUAL "") + message(FATAL_ERROR "Unable to locate 'base64-encode' utility") endif() - add_custom_command(OUTPUT ${_fileout} - COMMAND ${CMAKE_COMMAND} "-Dvarname=${_cfilein}" "-Dfilein=${_filein}" "-Dfileout=${_fileout}" -P "${_blob_to_source}" - DEPENDS "${_dirin}/${_filein}" "${_blob_to_source}" - WORKING_DIRECTORY "${_dirin}" - ) - unset(_cfilein) - unset(_blob_to_source) + else() # using build tree config + set(_BASE64_ENCODE KIM-API::base64-encode) + endif() + string(MAKE_C_IDENTIFIER ${_filein} _cfilein) + set(_edit_encode_output "${CMAKE_CURRENT_BINARY_DIR}/EditEncodeOutput.cmake") + if(NOT EXISTS "${_edit_encode_output}") + file(WRITE "${_edit_encode_output}" # use a bracket argument to avoid ugly escaping + [=[ + # This file was automatically generated by CMake; do not edit. + file(READ "${fileout}" _content) + string(REGEX REPLACE "unsigned char.*[[][]]" "unsigned char const ${varname}[]" _content "${_content}") + string(REGEX REPLACE "unsigned int.*=" "unsigned int const ${varname}_len =" _content "${_content}") + file(WRITE "${fileout}" "${_content}") + unset(_content) + ]=] + ) endif() -endfunction(_add_custom_command_blob_to_c) + add_custom_command(OUTPUT ${_fileout} + COMMAND ${_BASE64_ENCODE} -i "${_filein}" "${_fileout}" + COMMAND ${CMAKE_COMMAND} "-Dvarname=${_cfilein}" "-Dfilein=${_filein}" "-Dfileout=${_fileout}" -P "${_edit_encode_output}" + DEPENDS "${_dirin}/${_filein}" "${_edit_encode_output}" + WORKING_DIRECTORY "${_dirin}" + ) + unset(_cfilein) + unset(_edit_encode_output) + unset(_BASE64_ENCODE) +endfunction(_add_custom_command_blob_to_cpp) # # Function to create and define targets for c source files from provided data @@ -564,13 +549,13 @@ endfunction(_add_custom_command_blob_to_c) # Sets ITEM_*_SOURCES, ITEM_*_DECLARATIONS, ITEM_*_EMBEDDED_FILE_IDENTIFIER, # and ITEM_*_EMBEDDED_FILE_INITIALIZER_LIST variables in parent scope. # -function(_xxd_process_files) +function(_encode_process_files) set(_options "") set(_oneValueArgs FILE_TYPE) set(_multiValueArgs FILE_NAMES) cmake_parse_arguments(_ITEM "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) if(_ITEM_UNPARSED_ARGUMENTS) - message(FATAL_ERROR "Unparsed arguments found in macro '_xxd_process_files'") + message(FATAL_ERROR "Unparsed arguments found in macro '_encode_process_files'") endif() unset(_options) unset(_oneValueArgs) @@ -607,9 +592,9 @@ function(_xxd_process_files) else() message(FATAL_ERROR "Cannot find metadata file '${_FNAME}'") endif() - set(_FILE "${CMAKE_CURRENT_BINARY_DIR}/${_FNAME}.c") + set(_FILE "${CMAKE_CURRENT_BINARY_DIR}/${_FNAME}.cpp") list(APPEND _ITEM_SOURCES ${_FILE}) - _add_custom_command_blob_to_c("${_DIR_IN}" "${_FNAME}" "${_FILE}") + _add_custom_command_blob_to_cpp("${_DIR_IN}" "${_FNAME}" "${_FILE}") unset(_DIR_IN) endforeach() unset(_FNAME) @@ -620,7 +605,7 @@ function(_xxd_process_files) set(ITEM_${_ITEM_FILE_TYPE}_DECLARATIONS "${_ITEM_DECLARATIONS}" PARENT_SCOPE) set(ITEM_${_ITEM_FILE_TYPE}_EMBEDDED_FILE_IDENTIFIER "${_ITEM_EMBEDDED_FILE_IDENTIFIER}" PARENT_SCOPE) set(ITEM_${_ITEM_FILE_TYPE}_EMBEDDED_FILE_INITIALIZER_LIST "${_ITEM_EMBEDDED_FILE_INITIALIZER_LIST}" PARENT_SCOPE) -endfunction(_xxd_process_files) +endfunction(_encode_process_files) # # Function to set collection install prefix and item library file name. @@ -793,11 +778,11 @@ function(_add_kim_api_library) endif() set(_ITEM_SOURCES "") - _xxd_process_files(FILE_TYPE SM_SPEC_FILE FILE_NAMES "${ITEM_SM_SPEC_FILE}") + _encode_process_files(FILE_TYPE SM_SPEC_FILE FILE_NAMES "${ITEM_SM_SPEC_FILE}") list(APPEND _ITEM_SOURCES ${ITEM_SM_SPEC_FILE_SOURCES}) - _xxd_process_files(FILE_TYPE PARAMETER_FILE FILE_NAMES "${ITEM_PARAMETER_FILES}") + _encode_process_files(FILE_TYPE PARAMETER_FILE FILE_NAMES "${ITEM_PARAMETER_FILES}") list(APPEND _ITEM_SOURCES ${ITEM_PARAMETER_FILE_SOURCES}) - _xxd_process_files(FILE_TYPE METADATA_FILE FILE_NAMES "${ITEM_METADATA_FILES}") + _encode_process_files(FILE_TYPE METADATA_FILE FILE_NAMES "${ITEM_METADATA_FILES}") list(APPEND _ITEM_SOURCES ${ITEM_METADATA_FILE_SOURCES}) configure_file(${KIM-API-ITEMS_DIR}/item-info.txt.in ${CMAKE_CURRENT_BINARY_DIR}/item-info.txt @ONLY) diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index dce2430a..6d5b7205 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -35,6 +35,7 @@ target_include_directories(kim-api PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake # for KIM_SharedLibrarySchema.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/libb64/libb64/include ) configure_file(KIM_Configuration.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/KIM_Configuration.hpp) @@ -79,9 +80,15 @@ set(CPP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/KIM_LogImplementation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/KIM_ModelImplementation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/KIM_SharedLibrary.cpp - ) +) + +set(C_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/libb64/libb64/src/cdecode.c + ${CMAKE_CURRENT_SOURCE_DIR}/libb64/libb64/src/cencode.c +) +target_compile_definitions(kim-api PRIVATE BUFFERSIZE=${KIM_API_BASE64_BUFFERSIZE}) -target_sources(kim-api PRIVATE ${CPP_SOURCES}) +target_sources(kim-api PRIVATE ${CPP_SOURCES} ${C_SOURCES}) if(WIN32 AND NOT CYGWIN) # Activate C++17 language standard on Win32 platform, because kim-api code uses C++ filesystem library internally. diff --git a/cpp/src/KIM_SharedLibrary.cpp b/cpp/src/KIM_SharedLibrary.cpp index 32b09ff4..42943f9e 100644 --- a/cpp/src/KIM_SharedLibrary.cpp +++ b/cpp/src/KIM_SharedLibrary.cpp @@ -55,6 +55,10 @@ #include "KIM_SharedLibrarySchema.hpp" #endif +#ifndef BASE64_HPP +#include "b64/decode.h" // For base64 decoding +#endif + namespace { static void * const referencePointForKIM_Library = NULL; @@ -384,10 +388,7 @@ int SharedLibrary::Close() LOG_DEBUG("Exit 1=" + callString); return true; } - else - { - sharedLibraryHandle_ = NULL; - } + else { sharedLibraryHandle_ = NULL; } LOG_DEBUG("Exit 0=" + callString); return false; @@ -649,7 +650,12 @@ int SharedLibrary::WriteParameterFileDirectory() std::ofstream fl; fl.open(specificationFilePathName.string().c_str(), std::ifstream::out | std::ifstream::binary); - fl.write(reinterpret_cast(specificationData), len); + + std::istringstream strStream( + reinterpret_cast(specificationData)); + base64::decoder decoder = base64::decoder(); + decoder.decode(strStream, fl); + if (!fl) { LOG_ERROR("Unable to get write parameter file."); @@ -678,9 +684,15 @@ int SharedLibrary::WriteParameterFileDirectory() FILESYSTEM::Path const parameterFilePathName = parameterFileDirectoryName_ / parameterFileName; std::ofstream fl; + fl.open(parameterFilePathName.string().c_str(), std::ifstream::out | std::ifstream::binary); - fl.write(reinterpret_cast(strPtr), length); + + std::istringstream strStream(reinterpret_cast(strPtr)); + base64::decoder decoder = base64::decoder(); + + decoder.decode(strStream, fl); + if (!fl) { LOG_ERROR("Unable to get write parameter file."); diff --git a/cpp/src/libb64/LICENSE b/cpp/src/libb64/LICENSE new file mode 100644 index 00000000..a6b56069 --- /dev/null +++ b/cpp/src/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/cpp/src/libb64/README b/cpp/src/libb64/README new file mode 100644 index 00000000..be93b7b4 --- /dev/null +++ b/cpp/src/libb64/README @@ -0,0 +1,138 @@ +b64: Base64 Encoding/Decoding Routines +====================================== + +Overview: +-------- +libb64 is a library of ANSI C routines for fast encoding/decoding data into and +from a base64-encoded format. C++ wrappers are included, as well as the source +code for standalone encoding and decoding executables. + +base64 consists of ASCII text, and is therefore a useful encoding for storing +binary data in a text file, such as xml, or sending binary data over text-only +email. + +References: +---------- +* Wikipedia article: + http://en.wikipedia.org/wiki/Base64 +* base64, another implementation of a commandline en/decoder: + http://www.fourmilab.ch/webtools/base64/ + +Why? +--- +I did this because I need an implementation of base64 encoding and decoding, +without any licensing problems. Most OS implementations are released under +either the GNU/GPL, or a BSD-variant, which is not what I require. + +Also, the chance to actually use the co-routine implementation in code is rare, +and its use here is fitting. I couldn't pass up the chance. +For more information on this technique, see "Coroutines in C", by Simon Tatham, +which can be found online here: +http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + +So then, under which license do I release this code? On to the next section... + +License: +------- +This work is released under into the Public Domain. +It basically boils down to this: I put this work in the public domain, and you +can take it and do whatever you want with it. + +An example of this "license" is the Creative Commons Public Domain License, a +copy of which can be found in the LICENSE file, and also online at +http://creativecommons.org/licenses/publicdomain/ + +Commandline Use: +--------------- +There is a new executable available, it is simply called base64. +It can encode and decode files, as instructed by the user. + +To encode a file: +$ ./base64 -e filea fileb +fileb will now be the base64-encoded version of filea. + +To decode a file: +$ ./base64 -d fileb filec +filec will now be identical to filea. + +Programming: +----------- +Some C++ wrappers are provided as well, so you don't have to get your hands +dirty. Encoding from standard input to standard output is as simple as + + #include + #include + int main() + { + base64::encoder E; + E.encode(std::cin, std::cout); + return 0; + } + +Both standalone executables and a static library is provided in the package, + +Implementation: +-------------- +It is DAMN fast, if I may say so myself. The C code uses a little trick which +has been used to implement coroutines, of which one can say that this +implementation is an example. + +(To see how the libb64 codebase compares with some other BASE64 implementations +available, see the BENCHMARKS file) + +The trick involves the fact that a switch-statement may legally cross into +sub-blocks. A very thorough and enlightening essay on co-routines in C, using +this method, can be found in the above mentioned "Coroutines in C", by Simon +Tatham: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + +For example, an RLE decompressing routine, adapted from the article: +1 static int STATE = 0; +2 static int len, c; +3 switch (STATE) +4 { +5 while (1) +6 { +7 c = getchar(); +8 if (c == EOF) return EOF; +9 if (c == 0xFF) { +10 len = getchar(); +11 c = getchar(); +12 while (len--) +13 { +14 STATE = 0; +15 return c; +16 case 0: +17 } +18 } else +19 STATE = 1; +20 return c; +21 case 1: +22 } +23 } +24 } + +As can be seen from this example, a coroutine depends on a state variable, +which it sets directly before exiting (lines 14 and 119). The next time the +routine is entered, the switch moves control to the specific point directly +after the previous exit (lines 16 and 21).hands + +(As an aside, in the mentioned article the combination of the top-level switch, +the various setting of the state, the return of a value, and the labelling of +the exit point is wrapped in #define macros, making the structure of the +routine even clearer.) + +The obvious problem with any such routine is the static keyword. +Any static variables in a function spell doom for multithreaded applications. +Also, in situations where this coroutine is used by more than one other +coroutines, the consistency is disturbed. + +What is needed is a structure for storing these variabled, which is passed to +the routine seperately. This obviously breaks the modularity of the function, +since now the caller has to worry about and care for the internal state of the +routine (the callee). This allows for a fast, multithreading-enabled +implementation, which may (obviously) be wrapped in a C++ object for ease of +use. + +The base64 encoding and decoding functionality in this package is implemented +in exactly this way, providing both a high-speed high-maintanence C interface, +and a wrapped C++ which is low-maintanence and only slightly less performant. diff --git a/cpp/src/libb64/libb64.origin b/cpp/src/libb64/libb64.origin new file mode 100644 index 00000000..e5d7c790 --- /dev/null +++ b/cpp/src/libb64/libb64.origin @@ -0,0 +1,56 @@ +------------------------------------------------------------------------------ +libb64 — MODIFIED ORIGIN DETAILS +------------------------------------------------------------------------------ + +1. ORIGINAL SOURCE + Name/Version : libb64 1.2 + Original URL : https://sourceforge.net/projects/libb64/files/libb64/libb64/libb64-1.2.src.zip/download + Access Date : 01/02/2025 + Original Author : Chris Venter, chris.venter@gmail.com, http://rocketpod.blogspot.com + Adapted by : Navneeth Mohan, Amit Gupta + +2. MODIFICATIONS + Removed: + libb64-1.2/AUTHORS + libb64-1.2/base64/base64.cc + libb64-1.2/base64/Makefile + libb64-1.2/base64/VisualStudioProject/ + libb64-1.2/base64/VisualStudioProject/base64.sln + libb64-1.2/base64/VisualStudioProject/base64.vcxproj + libb64-1.2/base64/VisualStudioProject/base64.vcxproj.filters + libb64-1.2/base64/VisualStudioProject/Makefile + libb64-1.2/BENCHMARKS + libb64-1.2/CHANGELOG + libb64-1.2/LICENSE + libb64-1.2/Makefile + libb64-1.2/src/Makefile + libb64-1.2/TODO + +3. Patches + +1. `src/cencode.c` + +``` +< if (value_in < 0 || value_in >= decoding_size) return -1; +--- +> if (value_in < 0 || value_in > decoding_size) return -1; +``` + +2. `src/cencode.c` +``` +< *codechar++ = '\n'; +--- +> /* *codechar++ = '\n'; */ +``` + +3. `src/cdecode.c` + +``` +78d77 +< *codechar++ = '\"'; +80d78 +< *codechar++ = '\"'; +``` + +4. LICENSE + The original license is included in the file `LICENSE`. diff --git a/cpp/src/libb64/libb64/include/b64/cdecode.h b/cpp/src/libb64/libb64/include/b64/cdecode.h new file mode 100644 index 00000000..5729853d --- /dev/null +++ b/cpp/src/libb64/libb64/include/b64/cdecode.h @@ -0,0 +1,29 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ + diff --git a/cpp/src/libb64/libb64/include/b64/cencode.h b/cpp/src/libb64/libb64/include/b64/cencode.h new file mode 100644 index 00000000..cf321312 --- /dev/null +++ b/cpp/src/libb64/libb64/include/b64/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/cpp/src/libb64/libb64/include/b64/decode.h b/cpp/src/libb64/libb64/include/b64/decode.h new file mode 100644 index 00000000..12b16eac --- /dev/null +++ b/cpp/src/libb64/libb64/include/b64/decode.h @@ -0,0 +1,70 @@ +// :mode=c++: +/* +decode.h - c++ wrapper for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ +#ifndef BASE64_DECODE_H +#define BASE64_DECODE_H + +#include + +namespace base64 +{ + extern "C" + { + #include "cdecode.h" + } + + struct decoder + { + base64_decodestate _state; + int _buffersize; + + decoder(int buffersize_in = BUFFERSIZE) + : _buffersize(buffersize_in) + {} + + int decode(char value_in) + { + return base64_decode_value(value_in); + } + + int decode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_decode_block(code_in, length_in, plaintext_out, &_state); + } + + void decode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_decodestate(&_state); + // + const int N = _buffersize; + char* code = new char[N]; + char* plaintext = new char[N]; + int codelength; + int plainlength; + + do + { + istream_in.read((char*)code, N); + codelength = istream_in.gcount(); + plainlength = decode(code, codelength, plaintext); + ostream_in.write((const char*)plaintext, plainlength); + } + while (istream_in.good() && codelength > 0); + // + base64_init_decodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + + + +#endif // BASE64_DECODE_H + diff --git a/cpp/src/libb64/libb64/include/b64/encode.h b/cpp/src/libb64/libb64/include/b64/encode.h new file mode 100644 index 00000000..5d807d97 --- /dev/null +++ b/cpp/src/libb64/libb64/include/b64/encode.h @@ -0,0 +1,77 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include + +namespace base64 +{ + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = BUFFERSIZE) + : _buffersize(buffersize_in) + {} + + int encode(char value_in) + { + return base64_encode_value(value_in); + } + + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = istream_in.gcount(); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + diff --git a/cpp/src/libb64/libb64/src/cdecode.c b/cpp/src/libb64/libb64/src/cdecode.c new file mode 100644 index 00000000..a6c0a423 --- /dev/null +++ b/cpp/src/libb64/libb64/src/cdecode.c @@ -0,0 +1,88 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in >= decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + diff --git a/cpp/src/libb64/libb64/src/cencode.c b/cpp/src/libb64/libb64/src/cencode.c new file mode 100644 index 00000000..f9400a7d --- /dev/null +++ b/cpp/src/libb64/libb64/src/cencode.c @@ -0,0 +1,112 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\"'; + *codechar++ = '\n'; + *codechar++ = '\"'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + /* comment out this line to remove an excess new-line character */ + /* *codechar++ = '\n'; */ + + return codechar - code_out; +} + diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian index 187b60b3..6f0ad280 100644 --- a/docker/Dockerfile.debian +++ b/docker/Dockerfile.debian @@ -41,7 +41,7 @@ RUN apt-get -y update && \ gfortran \ git \ iwyu \ - libasan6 \ + libasan8 \ make \ wget \ xxd \ diff --git a/docker/Dockerfile.openSUSE b/docker/Dockerfile.openSUSE index 497b54b3..7045f362 100644 --- a/docker/Dockerfile.openSUSE +++ b/docker/Dockerfile.openSUSE @@ -41,7 +41,7 @@ RUN zypper -n install \ gcc-c++ \ gcc-fortran \ include-what-you-use \ - libasan6 \ + libasan8 \ make \ sysuser-shadow \ tar \ diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index e625cd05..909608e5 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -35,6 +35,7 @@ include(RelocatablePath) set(PORTABLE_MODEL_INFO ${PROJECT_NAME}-portable-model-info) set(SIMULATOR_MODEL ${PROJECT_NAME}-simulator-model) set(COLLECTIONS_INFO ${PROJECT_NAME}-collections-info) +set(BASE64_ENCODE ${PROJECT_NAME}-base64-encode) set(COLLECTIONS_MANAGEMENT ${PROJECT_NAME}-collections-management) set(SHARED_LIBRARY_TEST ${PROJECT_NAME}-shared-library-test) set(ACTIVATE_SCRIPT ${PROJECT_NAME}-activate) @@ -122,10 +123,16 @@ target_link_libraries(simulator-model kim-api) set_target_properties(simulator-model PROPERTIES OUTPUT_NAME ${SIMULATOR_MODEL}) add_executable(collections-info collections-info.cpp) -target_include_directories(collections-info PRIVATE "${PROJECT_SOURCE_DIR}/cpp/src/") +target_include_directories(collections-info PRIVATE "${PROJECT_SOURCE_DIR}/cpp/src") target_link_libraries(collections-info kim-api) set_target_properties(collections-info PROPERTIES OUTPUT_NAME ${COLLECTIONS_INFO}) +add_executable(base64-encode base64-encode.cpp ${PROJECT_SOURCE_DIR}/cpp/src/libb64/libb64/src/cencode.c) +target_include_directories(base64-encode PRIVATE "${PROJECT_BINARY_DIR}/cpp/include") +target_include_directories(base64-encode PRIVATE "${PROJECT_SOURCE_DIR}/cpp/src/libb64/libb64/include") +set_target_properties(base64-encode PROPERTIES OUTPUT_NAME ${BASE64_ENCODE}) +target_compile_definitions(base64-encode PRIVATE BUFFERSIZE=${KIM_API_BASE64_BUFFERSIZE}) + add_executable(shared-library-test shared-library-test.cpp) target_include_directories(shared-library-test PRIVATE "${PROJECT_BINARY_DIR}/cpp/include") target_link_libraries(shared-library-test ${CMAKE_DL_LIBS}) @@ -138,7 +145,7 @@ else() set(_dir "${CMAKE_INSTALL_RELOC_BINDIR}") endif() install( - TARGETS portable-model-info simulator-model collections-info shared-library-test + TARGETS portable-model-info simulator-model collections-info base64-encode shared-library-test EXPORT KIM_API_Targets DESTINATION "${_dir}" ) diff --git a/utils/base64-encode.cpp b/utils/base64-encode.cpp new file mode 100644 index 00000000..de6373e0 --- /dev/null +++ b/utils/base64-encode.cpp @@ -0,0 +1,130 @@ +// from base64.cc - c++ source to a base64 reference encoder and decoder + +// +// KIM-API: An API for interatomic models +// Copyright (c) 2013--2022, Regents of the University of Minnesota. +// All rights reserved. +// +// Contributors: +// Navneeth Mohan +// Amit Gupta +// +// SPDX-License-Identifier: LGPL-2.1-or-later +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this library; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +// +// Release: This file is part of the kim-api.git repository. +// + + +#include "KIM_Version.hpp" + +#include + +#include +#include +#include + +#include +#include +#include + +// Function which prints the usage of this executable +void usage(std::string name) +{ + size_t beg = name.find_last_of("/\\"); + if (beg != std::string::npos) name = name.substr(beg + 1, std::string::npos); + // Follows docopt.org format + std::cerr << "Usage:\n" + << " " << name << " -i \n" + << " " << name << " --version\n"; + // note: this interface is likely to change in future kim-api releases +} + +void usage(std::string name, std::string message) +{ + std::cerr << message << std::endl; + usage(name); +} + +// quick replacement of to_string for C++98 +inline std::string int_to_string(int value) +{ + std::ostringstream oss; + oss << value; + return oss.str(); +} + +int main(int argc, char ** argv) +{ + if ((argc < 2) || (argc > 4)) + { + usage(argv[0]); + return 1; + } + + std::string option = argv[1]; + if (option == "--version") + { + std::cout << KIM_VERSION_STRING << std::endl; + return 0; + } + else if (option != "-i" || argc != 4) + { + usage(argv[0]); + exit(-1); + } + + // open the input file in binary-mode + std::string input = argv[2]; + std::ifstream instream(input.c_str(), + std::ios_base::in | std::ios_base::binary); + if (!instream.is_open()) + { + usage(argv[0], "Could not open input file " + input); + exit(-1); + } + + // open the output file in binary-mode + std::string output = argv[3]; + std::ofstream outstream(output.c_str(), std::ios_base::out); + if (!outstream.is_open()) + { + usage(argv[0], "Could not open output file " + output); + exit(-1); + } + + std::string encodeFormatFileName = input; + std::replace( + encodeFormatFileName.begin(), encodeFormatFileName.end(), '.', '_'); + std::replace( + encodeFormatFileName.begin(), encodeFormatFileName.end(), '-', '_'); + + std::string header + = "extern unsigned char " + encodeFormatFileName + "[] = \n\""; + outstream.write(header.data(), header.length()); + + base64::encoder E; + + E.encode(instream, outstream); + std::string footer = "\";\nextern unsigned int " + encodeFormatFileName + + "_len = sizeof(" + encodeFormatFileName + ") - 1;\n"; + + outstream.write(footer.data(), footer.length()); + + return 0; +}