diff --git a/.clang-tidy b/.clang-tidy index e580864c1..4a4459c57 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -Checks: '*,-altera-*,-clang-analyzer-optin.performance.Padding,-bugprone-easily-swappable-parameters,-bugprone-assignment-in-if-condition,-clang-diagnostic-invalid-command-line-argument,-concurrency-mt-unsafe,-cppcoreguidelines*,-hicpp-*,-llvmlibc-restrict-system-libc-headers,-readability-identifier-length,-readability-function-cognitive-complexity,-google-readability-function-size,-readability-function-size,-readability-magic-numbers,-readability-non-const-parameter,-google-readability-todo' +Checks: '*,-clang-analyzer-optin.core.EnumCastOutOfRange,-readability-avoid-nested-conditional-operator,-bugprone-switch-missing-default-case,-misc-include-cleaner,-altera-*,-clang-analyzer-optin.performance.Padding,-bugprone-easily-swappable-parameters,-bugprone-assignment-in-if-condition,-clang-diagnostic-invalid-command-line-argument,-concurrency-mt-unsafe,-cppcoreguidelines*,-hicpp-*,-llvmlibc-restrict-system-libc-headers,-readability-identifier-length,-readability-function-cognitive-complexity,-google-readability-function-size,-readability-function-size,-readability-magic-numbers,-readability-non-const-parameter,-google-readability-todo' WarningsAsErrors: '' HeaderFilterRegex: '' FormatStyle: none diff --git a/.eslintrc-min.json b/.eslintrc-min.json deleted file mode 100644 index f1361d215..000000000 --- a/.eslintrc-min.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "extends": "eslint:recommended", - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly", - "BSN": true, - "i18n": true, - "MediaMetadata": true - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - "block-scoped-var": "error", - "camelcase": "error", - "default-case-last": "error", - "eqeqeq": ["error", "always", {"null": "ignore"}], - "no-alert": "error", - "no-caller": "error", - "no-console": "off", - "no-empty": "off", - "no-eq-null": "error", - "no-eval": "error", - "no-fallthrough": "off", - "no-implied-eval": "error", - "no-invalid-this": "error", - "no-redeclare": ["error", { "builtinGlobals": false }], - "no-restricted-globals": "error", - "no-restricted-properties": "error", - "no-return-assign": "error", - "no-self-compare": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-unused-vars": "off", - "no-useless-concat": "off", - "no-var": "error", - "prefer-const": "error" - } -} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bb461fe2f..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "plugins": [ - "jsdoc" - ], - "extends": [ - "eslint:recommended", - "plugin:jsdoc/recommended" - ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly", - "BSN": true, - "i18n": true, - "MediaMetadata": true - }, - "parserOptions": { - "ecmaVersion": 2018 - }, - "rules": { - "block-scoped-var": "error", - "camelcase": "error", - "default-case": "error", - "default-case-last": "error", - "eqeqeq": ["error", "always", { - "null": "ignore" - }], - "semi": [2, "always"], - "no-alert": "error", - "no-caller": "error", - "no-console": "off", - "no-eq-null": "error", - "no-eval": "error", - "no-implied-eval": "error", - "no-invalid-this": "error", - "no-restricted-globals": "error", - "no-restricted-properties": "error", - "no-return-assign": "error", - "no-self-compare": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-useless-concat": "error", - "no-var": "error", - "prefer-const": "error", - "jsdoc/check-syntax": 1, - "jsdoc/newline-after-description": "off", - "jsdoc/no-blank-block-descriptions": 1, - "jsdoc/no-defaults": 1, - "jsdoc/no-undefined-types": ["warn", { - "definedTypes": [ - "ChildNode", - "ParentNode" - ] - }], - "jsdoc/require-returns": ["error", { - "forceRequireReturn": true - }] - } -} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index db9cf1bca..fa90c706b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: - name: install linters run: | sudo apt-get update - sudo apt-get install -y npm cppcheck flawfinder clang-tidy + sudo apt-get install -y npm clang-tidy sudo ./build.sh installdeps npm install eslint --save-dev npm install stylelint --save-dev diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4197f82..db8f99ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ https://github.com/jcorporation/myMPD/ *** +## myMPD v15.0.0 (2024-05-02) + +This release reworks the table and grid layouts for all views. Each view can now be displayed as table or grid. + +### API changes + +- The `cols` param was renamed to `fields` +- MYMPD_API_PLAYLIST_LIST: response changed +- MYMPD_API_VIEW_SAVE: new +- MYMPD_API_COLS_SAVE: removed + +### Changelog + +- Feat: All views can be displayed as table or grid (with pictures) #1051 +- Feat: Add setting for default sort tag in library view #1207 +- Feat: Add lua library for myGPIOd support #1208 +- Feat: Support range for listplaylist and listplaylistinfo (MPD 0.24) #1214 +- Feat: Support playlistlength command (MPD 0.24) #1213 +- Feat: Add column for "File type" in song lists #1225 +- Feat: Add thumbnail column to views #1093 +- Feat: Add playlist art handler +- Feat: Configurable columns for playlist view +- Feat: Add option to disable covercache pruning #1237 +- Feat: Add support for sticker find operators contains, starts_with (MPD 0.24) +- Feat: Provide and install a systemd user unit #1262 +- Upd: Bootstrap 5.3.3 +- Upd: Support ESLint 9 +- Fix: List and search playlists in filesystem view +- Fix: SSL issues with iPhone clients + +*** + ## myMPD 14.1.2 (2024-04-15) This is a small bugfix release. @@ -35,6 +67,7 @@ This release enables the support for sticker sorting and fixes a severe mongoose - Feat: Sort sticker search results (MPD 0.24) #1094 - Fix: Problems with settings in 14.0.x #1221 - Fix: Browse filesystem layout #1235 +- Fix: Re-add custom css to mongoose dir listing *** diff --git a/CMakeLists.txt b/CMakeLists.txt index 81f84ff86..106778d4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ cmake_policy(SET CMP0003 NEW) # myMPD is written in C # supported compilers: gcc, clang project(mympd - VERSION 14.1.2 + VERSION 15.0.0 LANGUAGES C ) @@ -23,6 +23,7 @@ message("Cmake build type: ${CMAKE_BUILD_TYPE}") message("Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}") message("CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") message("CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}") +message("Arch: ${CMAKE_C_LIBRARY_ARCHITECTURE}") # reset cmake default flags set(CMAKE_C_FLAGS_DEBUG "") @@ -50,6 +51,7 @@ option(MYMPD_ENABLE_FLAC "Enables flac support, default ON" "ON") option(MYMPD_ENABLE_IPV6 "Enables IPv6, default ON" "ON") option(MYMPD_ENABLE_LIBID3TAG "Enables libid3tag support, default ON" "ON") option(MYMPD_ENABLE_LUA "Enables lua support, default ON" "ON") +option(MYMPD_ENABLE_MYGPIOD "Enables myGPIOd support, default ON" "ON") option(MYMPD_MANPAGES "Creates and installs manpages" "ON") option(MYMPD_MINIMAL "Enables minimal myMPD build, disables all MYMPD_ENABLE_* flags" "OFF") option(MYMPD_STARTUP_SCRIPT "Installs the startup script, default ON" "ON") @@ -63,6 +65,7 @@ if(MYMPD_MINIMAL) set(MYMPD_ENABLE_IPV6 "OFF") set(MYMPD_ENABLE_LUA "OFF") set(MYMPD_ENABLE_LIBID3TAG "OFF") + set(MYMPD_ENABLE_MYGPIOD "OFF") endif() if(MYMPD_ENABLE_EXPERIMENTAL) @@ -78,56 +81,6 @@ include(GNUInstallDirs) # custom cmake modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/") -# calculate paths -if(CMAKE_INSTALL_PREFIX MATCHES "/usr") - set(SUBDIR "/${PROJECT_NAME}") - set(SUBDIRLIB "/lib") - set(SUBDIRCACHE "/cache") -else() - # for install in /opt - set(SUBDIR "") - set(SUBDIRLIB "") - set(SUBDIRCACHE "") -endif() - -message("Executables in: ${CMAKE_INSTALL_FULL_BINDIR}") - -if(CMAKE_INSTALL_PREFIX STREQUAL "/usr/local") - set(MYMPD_WORK_DIR "/${CMAKE_INSTALL_LOCALSTATEDIR}${SUBDIRLIB}${SUBDIR}") - set(MYMPD_CACHE_DIR "/${CMAKE_INSTALL_LOCALSTATEDIR}${SUBDIRCACHE}${SUBDIR}") -else() - set(MYMPD_WORK_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}${SUBDIRLIB}${SUBDIR}") - set(MYMPD_CACHE_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}${SUBDIRCACHE}${SUBDIR}") -endif() - -message("Workdir: ${MYMPD_WORK_DIR}") -message("Cachedir: ${MYMPD_CACHE_DIR}") - -# create assets and set doc root -set(ENV{MYMPD_BUILDDIR} "${CMAKE_CURRENT_BINARY_DIR}") - -if(MYMPD_EMBEDDED_ASSETS) - message("Embedding assets in binary") - set(MYMPD_DOC_ROOT "${MYMPD_WORK_DIR}/empty") - set(MYMPD_LUALIBS_PATH "") - execute_process(COMMAND "${PROJECT_SOURCE_DIR}/build.sh" createassets RESULT_VARIABLE RC_CREATE_ASSETS) - # remove object files with embedded assets - file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/src/CMakeFiles/mympd.dir/web_server/utility.c.o") - file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/src/CMakeFiles/mympd.dir/mympd_api/scripts.c.o") -else() - message("Serving assets from filesystem") - set(MYMPD_DOC_ROOT "${PROJECT_SOURCE_DIR}/htdocs") - set(MYMPD_LUALIBS_PATH "${PROJECT_SOURCE_DIR}/contrib/lualibs") - execute_process(COMMAND "${PROJECT_SOURCE_DIR}/build.sh" copyassets RESULT_VARIABLE RC_CREATE_ASSETS) -endif() - -if(RC_CREATE_ASSETS GREATER 0) - message(FATAL_ERROR "Creating assets failed") -endif() - -message("Document root: ${MYMPD_DOC_ROOT}") -message("Docdir: ${CMAKE_INSTALL_FULL_DOCDIR}") - # required dependencies find_package(Threads REQUIRED) find_package(PCRE2 REQUIRED) @@ -176,11 +129,74 @@ if(MYMPD_ENABLE_LUA) else() message("Lua is disabled because it was not found") set(MYMPD_ENABLE_LUA "OFF") + set(MYMPD_ENABLE_MYGPIOD "OFF") endif() else() message("Lua is disabled by user") endif() +if(MYMPD_ENABLE_MYGPIOD) + message("Searching for libmygpio") + find_package(LIBMYGPIO) + if(NOT LIBMYGPIO_FOUND) + message("Compiling static version of libmygpio") + set(MYMPD_ENABLE_MYGPIOD_STATIC "ON") + endif() +else() + message("myGPIO support is disabled by user") +endif() + +# calculate paths +if(CMAKE_INSTALL_PREFIX MATCHES "/usr") + set(SUBDIR "/${PROJECT_NAME}") + set(SUBDIRLIB "/lib") + set(SUBDIRCACHE "/cache") +else() + # for install in /opt + set(SUBDIR "") + set(SUBDIRLIB "") + set(SUBDIRCACHE "") +endif() + +message("Executables in: ${CMAKE_INSTALL_FULL_BINDIR}") + +if(CMAKE_INSTALL_PREFIX STREQUAL "/usr/local") + set(MYMPD_WORK_DIR "/${CMAKE_INSTALL_LOCALSTATEDIR}${SUBDIRLIB}${SUBDIR}") + set(MYMPD_CACHE_DIR "/${CMAKE_INSTALL_LOCALSTATEDIR}${SUBDIRCACHE}${SUBDIR}") +else() + set(MYMPD_WORK_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}${SUBDIRLIB}${SUBDIR}") + set(MYMPD_CACHE_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}${SUBDIRCACHE}${SUBDIR}") +endif() + +message("Workdir: ${MYMPD_WORK_DIR}") +message("Cachedir: ${MYMPD_CACHE_DIR}") + +# create assets and set doc root +set(ENV{MYMPD_BUILDDIR} "${CMAKE_CURRENT_BINARY_DIR}") +set(ENV{MYMPD_ENABLE_MYGPIOD} "${MYMPD_ENABLE_MYGPIOD}") + +if(MYMPD_EMBEDDED_ASSETS) + message("Embedding assets in binary") + set(MYMPD_DOC_ROOT "${MYMPD_WORK_DIR}/empty") + set(MYMPD_LUALIBS_PATH "") + execute_process(COMMAND "${PROJECT_SOURCE_DIR}/build.sh" createassets RESULT_VARIABLE RC_CREATE_ASSETS) + # remove object files with embedded assets + file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/src/CMakeFiles/mympd.dir/web_server/utility.c.o") + file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/src/CMakeFiles/mympd.dir/mympd_api/scripts.c.o") +else() + message("Serving assets from filesystem") + set(MYMPD_DOC_ROOT "${PROJECT_SOURCE_DIR}/htdocs") + set(MYMPD_LUALIBS_PATH "${CMAKE_CURRENT_BINARY_DIR}/contrib/lualibs") + execute_process(COMMAND "${PROJECT_SOURCE_DIR}/build.sh" copyassets RESULT_VARIABLE RC_CREATE_ASSETS) +endif() + +if(RC_CREATE_ASSETS GREATER 0) + message(FATAL_ERROR "Creating assets failed") +endif() + +message("Document root: ${MYMPD_DOC_ROOT}") +message("Docdir: ${CMAKE_INSTALL_FULL_DOCDIR}") + # translation files if(MYMPD_EMBEDDED_ASSETS) if(EXISTS "${PROJECT_BINARY_DIR}/htdocs/assets/i18n/bg-BG.json.gz") @@ -244,7 +260,8 @@ endif() # configure some files - version and path information configure_file(src/compile_time.h.in "${PROJECT_BINARY_DIR}/compile_time.h") configure_file(cmake/Install.cmake.in cmake/Install.cmake @ONLY) -configure_file(contrib/initscripts/mympd.service.in contrib/initscripts/mympd.service @ONLY) +configure_file(contrib/initscripts/mympd.service.system.in contrib/initscripts/system/mympd.service @ONLY) +configure_file(contrib/initscripts/mympd.service.user.in contrib/initscripts/user/mympd.service @ONLY) configure_file(contrib/initscripts/mympd.sysVinit.in contrib/initscripts/mympd.sysVinit @ONLY) configure_file(contrib/initscripts/mympd.openrc.in contrib/initscripts/mympd.openrc @ONLY) configure_file(contrib/initscripts/mympd.freebsdrc.in contrib/initscripts/mympd.freebsdrc @ONLY) @@ -400,7 +417,7 @@ add_subdirectory("cli_tools") # link all together target_link_libraries(mympd - libmympdclient + mympdclient mjson mpack mongoose @@ -413,15 +430,22 @@ target_link_libraries(mympd ) # link optional dependencies -if(LIBID3TAG_FOUND) +if(MYMPD_ENABLE_LIBID3TAG) target_link_libraries(mympd ${LIBID3TAG_LIBRARIES}) endif() -if(FLAC_FOUND) +if(MYMPD_ENABLE_FLAC) target_link_libraries(mympd ${FLAC_LIBRARIES}) endif() -if(LUA_FOUND) +if(MYMPD_ENABLE_LUA) target_link_libraries(mympd ${LUA_LIBRARIES}) endif() +if(MYMPD_ENABLE_MYGPIOD) + if(MYMPD_ENABLE_MYGPIOD_STATIC) + target_link_libraries(mympd mygpio) + else() + target_link_libraries(mympd ${LIBMYGPIO_LIBRARIES}) + endif() +endif() # install install(TARGETS mympd DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) diff --git a/Doxyfile b/Doxyfile index 18faece2d..93c46b867 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.9.4 +# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -19,7 +19,8 @@ # configuration file: # doxygen -x [configFile] # Use doxygen to compare the used configuration file with the template -# configuration file without replacing the environment variables: +# configuration file without replacing the environment variables or CMake type +# replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- @@ -85,7 +86,7 @@ CREATE_SUBDIRS = NO # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed -# numer of 16 directories. +# number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. @@ -362,6 +363,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 5 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # 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 putting a % sign in front of the word or @@ -486,6 +498,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -567,7 +587,8 @@ 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. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -605,7 +626,8 @@ INTERNAL_DOCS = NO # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -857,11 +879,26 @@ WARN_IF_INCOMPLETE_DOC = YES WARN_NO_PARAMDOC = YES +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -913,10 +950,21 @@ INPUT = src # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # 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. @@ -928,12 +976,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# 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++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1017,9 +1065,6 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, 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 = __attribute__ @@ -1064,6 +1109,11 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1105,6 +1155,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1242,10 +1301,11 @@ CLANG_DATABASE_PATH = ALPHABETICAL_INDEX = YES -# 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. +# 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. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1324,7 +1384,12 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1339,6 +1404,19 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see @@ -1369,15 +1447,6 @@ HTML_COLORSTYLE_SAT = 100 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 YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1397,6 +1466,13 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + # 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 @@ -1527,6 +1603,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # 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 @@ -1702,17 +1788,6 @@ HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT 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 - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -2026,9 +2101,16 @@ PDF_HYPERLINKS = 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. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2049,14 +2131,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# 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: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2222,13 +2296,39 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.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 Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database 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: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2371,15 +2471,15 @@ TAGFILES = 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. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # 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 +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2393,16 +2493,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# 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. @@ -2411,7 +2504,7 @@ 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 +# https://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: YES. @@ -2428,37 +2521,51 @@ HAVE_DOT = 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. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification 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. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# 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. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=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. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2466,15 +2573,21 @@ CLASS_GRAPH = YES # 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. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # 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. See also the chapter Grouping -# in the manual. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2534,7 +2647,9 @@ 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. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2543,7 +2658,10 @@ 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. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2583,7 +2701,10 @@ 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. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2599,7 +2720,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # 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). @@ -2637,11 +2758,12 @@ DOT_PATH = 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). +# 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. -MSCFILE_DIRS = +DIA_PATH = # 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 @@ -2691,18 +2813,6 @@ DOT_GRAPH_MAX_NODES = 100 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 to 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 @@ -2730,3 +2840,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# 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 = diff --git a/build.sh b/build.sh index 58098cc61..9ef541ec4 100755 --- a/build.sh +++ b/build.sh @@ -281,10 +281,22 @@ createassets() { cp -v dist/material-icons/MaterialIcons-Regular.woff2 "$MYMPD_BUILDDIR/htdocs/assets/" $ZIPCAT dist/material-icons/ligatures.json > "$MYMPD_BUILDDIR/htdocs/assets/ligatures.json.gz" + lualibs + + return 0 +} + +lualibs() { + [ -z "${MYMPD_ENABLE_MYGPIOD+x}" ] && MYMPD_ENABLE_MYGPIOD="OFF" echo "Copy integrated lua libraries" mkdir -p "$MYMPD_BUILDDIR/contrib/lualibs" - cp -v contrib/lualibs/*.lua "$MYMPD_BUILDDIR/contrib/lualibs/" - return 0 + cp -v contrib/lualibs/json.lua "$MYMPD_BUILDDIR/contrib/lualibs/" + cp -v contrib/lualibs/mympd/00-start.lua "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" + cat contrib/lualibs/mympd/10-mympd.lua >> "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" + cat contrib/lualibs/mympd/20-http_client.lua >> "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" + cat contrib/lualibs/mympd/30-execute.lua >> "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" + [ "$MYMPD_ENABLE_MYGPIOD" = "ON" ] && cat contrib/lualibs/mympd/40-mygpiod.lua >> "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" + cat contrib/lualibs/mympd/99-end.lua >> "$MYMPD_BUILDDIR/contrib/lualibs/mympd.lua" } buildrelease() { @@ -356,6 +368,8 @@ copyassets() { cp -v "$STARTPATH/dist/material-icons/MaterialIcons-Regular.woff2" "$STARTPATH/htdocs/assets/MaterialIcons-Regular.woff2" cp -v "$STARTPATH/dist/material-icons/ligatures.json" "$STARTPATH/htdocs/assets/ligatures.json" + lualibs + #Create defines create_js_defines @@ -544,52 +558,6 @@ check() { return 1 fi - if check_cmd cppcheck - then - echo "Running cppcheck" - [ -z "${CPPCHECKOPTS+z}" ] && CPPCHECKOPTS="-q --force --enable=warning" - find ./src/ -name \*.c | while read -r FILE - do - [ "$FILE" = "./src/mympd_api/scripts_lualibs.c" ] && continue - [ "$FILE" = "./src/web_server/embedded_files.c" ] && continue - #shellcheck disable=SC2086 - if ! cppcheck $CPPCHECKOPTS --error-exitcode=1 "$FILE" - then - return 1 - fi - done - find ./src/ -name \*.h | while read -r FILE - do - #shellcheck disable=SC2086 - if ! cppcheck $CPPCHECKOPTS --error-exitcode=1 "$FILE" - then - return 1 - fi - done - else - echo_warn "cppcheck not found" - return 1 - fi - - if check_cmd flawfinder - then - echo "Running flawfinder" - [ -z "${FLAWFINDEROPTS+z}" ] && FLAWFINDEROPTS="-m3 --quiet --dataonly" - #shellcheck disable=SC2086 - if ! flawfinder $FLAWFINDEROPTS --error-level=3 src - then - return 1 - fi - #shellcheck disable=SC2086 - if ! flawfinder $FLAWFINDEROPTS --error-level=3 cli_tools - then - return 1 - fi - else - echo_warn "flawfinder not found" - return 1 - fi - if [ ! -f src/compile_commands.json ] then echo "src/compile_commands.json not found" @@ -771,7 +739,12 @@ pkgarch() { } pkgosc() { - check_cmd osc + [ -z "${OSC_BIN+x}" ] && OSC_BIN="$HOME/python-venv/bin/osc" + if [ ! -x "$OSC_BIN" ] + then + echo_error "Command osc not found: $HOME/python-venv/bin/osc" + exit 1 + fi cleanup cleanuposc if [ -z "${OSC_REPO+x}" ] @@ -786,7 +759,7 @@ pkgosc() { mkdir osc cd osc || exit 1 - osc checkout "$OSC_REPO" + $OSC_BIN checkout "$OSC_REPO" rm -f "$OSC_REPO"/* cd "$STARTPATH" || exit 1 @@ -814,10 +787,10 @@ pkgosc() { cp ../contrib/packaging/arch/PKGBUILD "$OSC_REPO/" cd "$OSC_REPO" || exit 1 - osc addremove - osc st - osc vc -m "Update" - osc commit -m "Update" + $OSC_BIN addremove + $OSC_BIN st + $OSC_BIN vc -m "Update" + $OSC_BIN commit -m "Update" } installdeps() { @@ -1260,7 +1233,7 @@ run_eslint() { for F in release/htdocs/sw.min.js release/htdocs/js/mympd.min.js release/htdocs/js/i18n.min.js do echo "Linting $F" - if ! npx eslint --no-eslintrc -c .eslintrc-min.json $F + if ! npx eslint $F then rc=1 fi diff --git a/cmake/FindLIBMYGPIO.cmake b/cmake/FindLIBMYGPIO.cmake new file mode 100644 index 000000000..bf864a12f --- /dev/null +++ b/cmake/FindLIBMYGPIO.cmake @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# myMPD (c) 2018-2024 Juergen Mang +# https://github.com/jcorporation/mympd + +# Try to find libmygpio +# +# LIBMYGPIO_FOUND +# LIBMYGPIO_INCLUDE_DIRS +# LIBMYGPIO_LIBRARIES + +find_package(PkgConfig) +pkg_check_modules(PC_LIBMYGPIO QUIET libmygpio) + +# Look for the header file +find_path(LIBMYGPIO_INCLUDE_DIR + NAMES libmygpio.h + HINTS ${PC_LIBMYGPIO_INCLUDEDIR} ${PC_LIBMYGPIO_INCLUDE_DIRS} +) + +# Look for the library +find_library(LIBMYGPIO_LIBRARY + NAMES mygpio + HINTS ${PC_LIBMYGPIO_LIBDIR} ${PC_LIBMYGPIO_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBMYGPIO DEFAULT_MSG + LIBMYGPIO_LIBRARY LIBMYGPIO_INCLUDE_DIR +) + +# Copy the results to the output variables +if(LIBMYGPIO_FOUND) + set(LIBMYGPIO_LIBRARIES ${LIBMYGPIO_LIBRARY}) + set(LIBMYGPIO_INCLUDE_DIRS ${LIBMYGPIO_INCLUDE_DIR}) +else() + set(LIBMYGPIO_LIBRARIES) + set(LIBMYGPIO_INCLUDE_DIRS) +endif() + +mark_as_advanced(LIBMYGPIO_INCLUDE_DIRS LIBMYGPIO_LIBRARIES) diff --git a/cmake/Install.cmake.in b/cmake/Install.cmake.in index 37bcba919..cf14fd957 100644 --- a/cmake/Install.cmake.in +++ b/cmake/Install.cmake.in @@ -4,9 +4,12 @@ # Install startup script function(install_systemd) - file(INSTALL "@CMAKE_CURRENT_BINARY_DIR@/contrib/initscripts/mympd.service" + file(INSTALL "@CMAKE_CURRENT_BINARY_DIR@/contrib/initscripts/system/mympd.service" DESTINATION "/usr/lib/systemd/system" FILE_PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) + file(INSTALL "@CMAKE_CURRENT_BINARY_DIR@/contrib/initscripts/user/mympd.service" + DESTINATION "/usr/lib/systemd/user" + FILE_PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) endfunction() function(install_openrc) diff --git a/contrib/initscripts/mympd.service.in b/contrib/initscripts/mympd.service.system.in similarity index 100% rename from contrib/initscripts/mympd.service.in rename to contrib/initscripts/mympd.service.system.in diff --git a/contrib/initscripts/mympd.service.user.in b/contrib/initscripts/mympd.service.user.in new file mode 100644 index 000000000..56835e549 --- /dev/null +++ b/contrib/initscripts/mympd.service.user.in @@ -0,0 +1,33 @@ +# +# SPDX-License-Identifier: GPL-3.0-or-later +# myMPD (c) 2018-2024 Juergen Mang +# https://github.com/jcorporation/mympd +# +[Unit] +Description=myMPD server daemon +Documentation=man:mympd(1) +After=mpd.service + +[Service] +ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/mympd -w %h/.config/mympd -a %h/.cache/mympd +Environment="MYMPD_HTTP_PORT=8080" +Environment="MYMPD_SSL_PORT=8443" +Restart=always +WorkingDirectory=%h/.config/mympd +LockPersonality=yes +MemoryDenyWriteExecute=yes +PrivateDevices=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectProc=invisible +RestrictRealtime=yes +RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service + +[Install] +WantedBy=default.target diff --git a/contrib/lualibs/mympd.lua b/contrib/lualibs/mympd.lua deleted file mode 100644 index 8c1056453..000000000 --- a/contrib/lualibs/mympd.lua +++ /dev/null @@ -1,62 +0,0 @@ --- --- mympd.lua --- --- SPDX-License-Identifier: GPL-3.0-or-later --- myMPD (c) 2018-2024 Juergen Mang --- https://github.com/jcorporation/mympd --- - -mympd = { _version = "0.4.0" } - --- --- Calls the myMPD jsonrpc api --- -function mympd.api(method, params) - rc, raw_result = mympd_api(method, json.encode(params)) - result = json.decode(raw_result) - if rc == 0 then - return rc, result["result"] - end - return rc, result["error"] -end - --- --- Simple HTTP client --- -function mympd.http_client(method, uri, headers, payload) - rc, code, header, body = mympd_api_http_client(method, uri, headers, payload) - return rc, code, header, body -end - --- --- Populate the global mympd_state lua table --- -function mympd.init() - return mympd.api("INTERNAL_API_SCRIPT_INIT") -end - --- --- Execute a system command and capture its output --- -function mympd.os_capture(cmd) - if io then - local handle = assert(io.popen(cmd, 'r')) - local output = assert(handle:read('*a')) - handle:close() - - output = string.gsub( - string.gsub( - string.gsub(output, '^%s+', ''), - '%s+$', - '' - ), - '[\n\r]+', - ' ' - ) - return output - else - return "io library must be loaded" - end -end - -return mympd diff --git a/contrib/lualibs/mympd/00-start.lua b/contrib/lualibs/mympd/00-start.lua new file mode 100644 index 000000000..2ec8208b5 --- /dev/null +++ b/contrib/lualibs/mympd/00-start.lua @@ -0,0 +1,9 @@ +-- +-- mympd.lua +-- +-- SPDX-License-Identifier: GPL-3.0-or-later +-- myMPD (c) 2018-2024 Juergen Mang +-- https://github.com/jcorporation/mympd +-- + +mympd = { _version = "0.5.0" } diff --git a/contrib/lualibs/mympd/10-mympd.lua b/contrib/lualibs/mympd/10-mympd.lua new file mode 100644 index 000000000..be44873cb --- /dev/null +++ b/contrib/lualibs/mympd/10-mympd.lua @@ -0,0 +1,18 @@ +-- +-- Populate the global mympd_state lua table +-- +function mympd.init() + return mympd.api("INTERNAL_API_SCRIPT_INIT") +end + +-- +-- Calls the myMPD jsonrpc api +-- +function mympd.api(method, params) + rc, raw_result = mympd_api(method, json.encode(params)) + result = json.decode(raw_result) + if rc == 0 then + return rc, result["result"] + end + return rc, result["error"] +end diff --git a/contrib/lualibs/mympd/20-http_client.lua b/contrib/lualibs/mympd/20-http_client.lua new file mode 100644 index 000000000..8e619e451 --- /dev/null +++ b/contrib/lualibs/mympd/20-http_client.lua @@ -0,0 +1,7 @@ +-- +-- Simple HTTP client +-- +function mympd.http_client(method, uri, headers, payload) + rc, code, header, body = mympd_api_http_client(method, uri, headers, payload) + return rc, code, header, body +end diff --git a/contrib/lualibs/mympd/30-execute.lua b/contrib/lualibs/mympd/30-execute.lua new file mode 100644 index 000000000..e0271105e --- /dev/null +++ b/contrib/lualibs/mympd/30-execute.lua @@ -0,0 +1,23 @@ +-- +-- Execute a system command and capture its output +-- +function mympd.os_capture(cmd) + if io then + local handle = assert(io.popen(cmd, 'r')) + local output = assert(handle:read('*a')) + handle:close() + + output = string.gsub( + string.gsub( + string.gsub(output, '^%s+', ''), + '%s+$', + '' + ), + '[\n\r]+', + ' ' + ) + return output + else + return "io library must be loaded" + end +end diff --git a/contrib/lualibs/mympd/40-mygpiod.lua b/contrib/lualibs/mympd/40-mygpiod.lua new file mode 100644 index 000000000..0fa663083 --- /dev/null +++ b/contrib/lualibs/mympd/40-mygpiod.lua @@ -0,0 +1,35 @@ +--- +--- myGPIOd integration functions +--- This functions requires a running myGPIOd service. +--- myMPD must be compiled with libmygpio. +--- + +mympd.mygpiod_socket = "/run/mygpiod/socket" + +-- +-- Blinks a GPIO with given timeout and interval +-- +function mympd.gpio_blink(gpio, timeout_ms, interval_ms) + return mygpio_gpio_blink(mympd.mygpiod_socket, gpio, timeout_ms, interval_ms) +end + +-- +-- Returns the active state of a GPIO +-- +function mympd.gpio_get(gpio) + return mygpio_gpio_get(mympd.mygpiod_socket, gpio) +end + +-- +-- Sets the active value of a GPIO +-- +function mympd.gpio_set(gpio, value) + return mygpio_gpio_set(mympd.mygpiod_socket, gpio, value) +end + +-- +-- Toggles the active value of a GPIO +-- +function mympd.gpio_toggle(gpio) + return mygpio_gpio_toggle(mympd.mygpiod_socket, gpio) +end diff --git a/contrib/lualibs/mympd/99-end.lua b/contrib/lualibs/mympd/99-end.lua new file mode 100644 index 000000000..7864066f8 --- /dev/null +++ b/contrib/lualibs/mympd/99-end.lua @@ -0,0 +1,2 @@ + +return mympd diff --git a/contrib/man/mympd-config.1 b/contrib/man/mympd-config.1 index 29b8d8442..0924c7573 100644 --- a/contrib/man/mympd-config.1 +++ b/contrib/man/mympd-config.1 @@ -1,6 +1,6 @@ .\" Manpage for mympd-config. .\" Contact to correct errors or typos. -.TH man 1 "15 Apr 2024" "14.1.2" "mympd-config man page" +.TH man 1 "02 May 2024" "15.0.0" "mympd-config man page" .SH NAME mympd-config \- mympd configuration tool diff --git a/contrib/man/mympd-script.1 b/contrib/man/mympd-script.1 index d2e1cc908..e21eda620 100644 --- a/contrib/man/mympd-script.1 +++ b/contrib/man/mympd-script.1 @@ -1,6 +1,6 @@ .\" Manpage for mympd-script. .\" Contact to correct errors or typos. -.TH man 1 "15 Apr 2024" "14.1.2" "mympd-script man page" +.TH man 1 "02 May 2024" "15.0.0" "mympd-script man page" .SH NAME mympd-script \- mympd command line tool to execute scripts diff --git a/contrib/man/mympd.1 b/contrib/man/mympd.1 index 6eabb9a17..da0c012c7 100644 --- a/contrib/man/mympd.1 +++ b/contrib/man/mympd.1 @@ -1,6 +1,6 @@ .\" Manpage for mympd. .\" Contact to correct errors or typos. -.TH man 1 "15 Apr 2024" "14.1.2" "mympd man page" +.TH man 1 "02 May 2024" "15.0.0" "mympd man page" .SH NAME myMPD \- standalone and mobile friendly web mpd client diff --git a/contrib/packaging/alpine/APKBUILD b/contrib/packaging/alpine/APKBUILD index 63853ce81..ef2e2bf49 100644 --- a/contrib/packaging/alpine/APKBUILD +++ b/contrib/packaging/alpine/APKBUILD @@ -6,7 +6,7 @@ # Maintainer: Juergen Mang # pkgname=mympd -pkgver=14.1.2 +pkgver=15.0.0 pkgrel=0 pkgdesc="myMPD is a standalone and mobile friendly web-based MPD client." url="https://jcorporation.github.io/myMPD/" diff --git a/contrib/packaging/arch/PKGBUILD b/contrib/packaging/arch/PKGBUILD index 0c0c049e0..fbaef0aa2 100644 --- a/contrib/packaging/arch/PKGBUILD +++ b/contrib/packaging/arch/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Juergen Mang pkgname=mympd -pkgver=14.1.2 +pkgver=15.0.0 pkgrel=1 pkgdesc="A standalone and mobile friendly web-based MPD client." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/packaging/debian/changelog b/contrib/packaging/debian/changelog index eff0f23fa..e0c114d3d 100644 --- a/contrib/packaging/debian/changelog +++ b/contrib/packaging/debian/changelog @@ -1,5 +1,5 @@ -mympd (14.1.2-1) unstable; urgency=medium +mympd (15.0.0-1) unstable; urgency=medium * Release from master - -- Juergen Mang Mon, 15 Apr 2024 20:04:47 +0200 + -- Juergen Mang Thu, 02 May 2024 18:05:39 +0200 diff --git a/contrib/packaging/docker/Dockerfile.alpine b/contrib/packaging/docker/Dockerfile.alpine index 36b4c1915..6b3ccb2d4 100644 --- a/contrib/packaging/docker/Dockerfile.alpine +++ b/contrib/packaging/docker/Dockerfile.alpine @@ -4,12 +4,15 @@ # https://github.com/jcorporation/mympd # FROM alpine:latest as build + +WORKDIR / +RUN apk add --no-cache git alpine-sdk cmake + COPY . /myMPD/ WORKDIR /myMPD RUN ./build.sh installdeps RUN cmake -B "release" -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Release -DMYMPD_MANPAGES=OFF -DMYMPD_DOC=OFF . RUN make -j4 -C "release" -RUN make -C "release" install FROM alpine:latest RUN apk add --no-cache openssl libid3tag flac lua5.4 pcre2 newt diff --git a/contrib/packaging/docker/Dockerfile.alpine.debug b/contrib/packaging/docker/Dockerfile.alpine.debug index 28f79fdc8..f59304a69 100644 --- a/contrib/packaging/docker/Dockerfile.alpine.debug +++ b/contrib/packaging/docker/Dockerfile.alpine.debug @@ -4,12 +4,14 @@ # https://github.com/jcorporation/mympd # FROM alpine:latest + +WORKDIR / +RUN apk add --no-cache git alpine-sdk cmake + COPY . /myMPD/ WORKDIR /myMPD RUN ./build.sh installdeps RUN ./build.sh debug RUN ./build.sh addmympduser -ENV MPD_HOST=127.0.0.1 -ENV MPD_PORT=6600 ENTRYPOINT ["/myMPD/debug/mympd"] diff --git a/contrib/packaging/freebsd/multimedia/mympd/Makefile b/contrib/packaging/freebsd/multimedia/mympd/Makefile index 38bc63274..0e89b4765 100644 --- a/contrib/packaging/freebsd/multimedia/mympd/Makefile +++ b/contrib/packaging/freebsd/multimedia/mympd/Makefile @@ -1,6 +1,6 @@ PORTNAME= myMPD DISTVERSIONPREFIX= v -DISTVERSION= 14.1.2 +DISTVERSION= 15.0.0 CATEGORIES= multimedia MAINTAINER= robert.david@posteo.net diff --git a/contrib/packaging/gentoo/media-sound/mympd/mympd-14.1.2.ebuild b/contrib/packaging/gentoo/media-sound/mympd/mympd-15.0.0.ebuild similarity index 91% rename from contrib/packaging/gentoo/media-sound/mympd/mympd-14.1.2.ebuild rename to contrib/packaging/gentoo/media-sound/mympd/mympd-15.0.0.ebuild index 8ea9f926a..15c57ada1 100644 --- a/contrib/packaging/gentoo/media-sound/mympd/mympd-14.1.2.ebuild +++ b/contrib/packaging/gentoo/media-sound/mympd/mympd-15.0.0.ebuild @@ -57,7 +57,8 @@ src_install() { fi newinitd "contrib/initscripts/mympd.openrc" "${PN}" if use systemd; then - systemd_newunit contrib/initscripts/mympd.service mympd.service + systemd_newunit contrib/initscripts/system/mympd.service mympd.service + systemd_newuserunit contrib/initscripts/user/mympd.service mympd.service fi dodoc ${S}/README.md } diff --git a/contrib/packaging/openwrt/Makefile b/contrib/packaging/openwrt/Makefile index 3cdc4418f..130716a93 100644 --- a/contrib/packaging/openwrt/Makefile +++ b/contrib/packaging/openwrt/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME := mympd -PKG_VERSION := 14.1.2 +PKG_VERSION := 15.0.0 PKG_RELEASE := 1 PKG_SOURCE := $(PKG_NAME)-$(PKG_VERSION).tar.gz diff --git a/contrib/packaging/rpm/mympd.spec b/contrib/packaging/rpm/mympd.spec index da12cc515..41acb2a21 100644 --- a/contrib/packaging/rpm/mympd.spec +++ b/contrib/packaging/rpm/mympd.spec @@ -4,7 +4,7 @@ # (c) 2018-2024 Juergen Mang Name: mympd -Version: 14.1.2 +Version: 15.0.0 Release: 0 License: GPL-3.0-or-later Group: Productivity/Multimedia/Sound/Players @@ -59,6 +59,7 @@ fi /usr/bin/mympd-config /usr/bin/mympd-script /usr/lib/systemd/system/mympd.service +/usr/lib/systemd/user/mympd.service %{_mandir}/man1/mympd.1.gz %{_mandir}/man1/mympd-config.1.gz %{_mandir}/man1/mympd-script.1.gz @@ -69,5 +70,5 @@ fi %license LICENSE.md %changelog -* Mon Apr 15 2024 Juergen Mang 14.1.2-0 +* Thu May 02 2024 Juergen Mang 15.0.0-0 - Version from master diff --git a/contrib/packaging/rpm/mympd.spec.in b/contrib/packaging/rpm/mympd.spec.in index cff7dc271..3dd3eb3be 100644 --- a/contrib/packaging/rpm/mympd.spec.in +++ b/contrib/packaging/rpm/mympd.spec.in @@ -59,6 +59,7 @@ fi /usr/bin/mympd-config /usr/bin/mympd-script /usr/lib/systemd/system/mympd.service +/usr/lib/systemd/user/mympd.service %{_mandir}/man1/mympd.1.gz %{_mandir}/man1/mympd-config.1.gz %{_mandir}/man1/mympd-script.1.gz diff --git a/dist/CMakeLists.txt b/dist/CMakeLists.txt index 2c776cb6a..dd28368bd 100644 --- a/dist/CMakeLists.txt +++ b/dist/CMakeLists.txt @@ -8,3 +8,7 @@ add_subdirectory(mpack) add_subdirectory(mongoose) add_subdirectory(rax) add_subdirectory(sds) + +if(MYMPD_ENABLE_MYGPIOD_STATIC) + add_subdirectory(myGPIOd) +endif() diff --git a/dist/bootstrap/compiled/custom.css b/dist/bootstrap/compiled/custom.css index ed68fba3b..244352735 100644 --- a/dist/bootstrap/compiled/custom.css +++ b/dist/bootstrap/compiled/custom.css @@ -1,2 +1,2 @@ -:root,[data-bs-theme="light"]{--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #28a745;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-secondary: #6c757d;--bs-success: #28a745;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #343a40;--bs-secondary-rgb: 108,117,125;--bs-success-rgb: 40,167,69;--bs-warning-rgb: 255,193,7;--bs-danger-rgb: 220,53,69;--bs-light-rgb: 248,249,250;--bs-dark-rgb: 52,58,64;--bs-primary-text-emphasis: #10431c;--bs-secondary-text-emphasis: #2b2f32;--bs-success-text-emphasis: #10431c;--bs-info-text-emphasis: #055160;--bs-warning-text-emphasis: #664d03;--bs-danger-text-emphasis: #58151c;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4edda;--bs-secondary-bg-subtle: #e2e3e5;--bs-success-bg-subtle: #d4edda;--bs-info-bg-subtle: #cff4fc;--bs-warning-bg-subtle: #fff3cd;--bs-danger-bg-subtle: #f8d7da;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9dcb5;--bs-secondary-border-subtle: #c4c8cb;--bs-success-border-subtle: #a9dcb5;--bs-info-border-subtle: #9eeaf9;--bs-warning-border-subtle: #ffe69c;--bs-danger-border-subtle: #f1aeb5;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255,255,255;--bs-black-rgb: 0,0,0;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255,255,255,0.15), rgba(255,255,255,0));--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-color-rgb: 33,37,41;--bs-body-bg: #f8f9fa;--bs-body-bg-rgb: 248,249,250;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0,0,0;--bs-secondary-color: rgba(33,37,41,0.75);--bs-secondary-color-rgb: 33,37,41;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233,236,239;--bs-tertiary-color: rgba(33,37,41,0.5);--bs-tertiary-color-rgb: 33,37,41;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248,249,250;--bs-heading-color: inherit;--bs-link-color: #212529;--bs-link-color-rgb: 33,37,41;--bs-link-decoration: none;--bs-link-hover-color: #1a1e21;--bs-link-hover-color-rgb: 26,30,33;--bs-code-color: #d63384;--bs-highlight-color: #212529;--bs-highlight-bg: #fff3cd;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #dee2e6;--bs-border-color-translucent: rgba(0,0,0,0.175);--bs-border-radius: .375rem;--bs-border-radius-sm: .25rem;--bs-border-radius-lg: .5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: none;--bs-box-shadow-sm: none;--bs-box-shadow-lg: none;--bs-box-shadow-inset: none;--bs-focus-ring-width: .25rem;--bs-focus-ring-opacity: .25;--bs-focus-ring-color: rgba(40,167,69,0.25);--bs-form-valid-color: #28a745;--bs-form-valid-border-color: #28a745;--bs-form-invalid-color: #dc3545;--bs-form-invalid-border-color: #dc3545}[data-bs-theme="dark"]{color-scheme:dark;--bs-body-color: #f8f9fa;--bs-body-color-rgb: 248,249,250;--bs-body-bg: #343a40;--bs-body-bg-rgb: 52,58,64;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255,255,255;--bs-secondary-color: rgba(248,249,250,0.75);--bs-secondary-color-rgb: 248,249,250;--bs-secondary-bg: #343a40;--bs-secondary-bg-rgb: 52,58,64;--bs-tertiary-color: rgba(248,249,250,0.5);--bs-tertiary-color-rgb: 248,249,250;--bs-tertiary-bg: #2b3035;--bs-tertiary-bg-rgb: 43,48,53;--bs-primary-text-emphasis: #7eca8f;--bs-secondary-text-emphasis: #a7acb1;--bs-success-text-emphasis: #208637;--bs-info-text-emphasis: #6edff6;--bs-warning-text-emphasis: #664d03;--bs-danger-text-emphasis: #b02a37;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #08210e;--bs-secondary-bg-subtle: #161719;--bs-success-bg-subtle: #d4edda;--bs-info-bg-subtle: #032830;--bs-warning-bg-subtle: #fff3cd;--bs-danger-bg-subtle: #f8d7da;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #186429;--bs-secondary-border-subtle: #41464b;--bs-success-border-subtle: #a9dcb5;--bs-info-border-subtle: #087990;--bs-warning-border-subtle: #ffe69c;--bs-danger-border-subtle: #f1aeb5;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #f8f9fa;--bs-link-hover-color: #f9fafb;--bs-link-color-rgb: 248,249,250;--bs-link-hover-color-rgb: 249,250,251;--bs-code-color: #e685b5;--bs-highlight-color: #f8f9fa;--bs-highlight-bg: #664d03;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255,255,255,0.15);--bs-form-valid-color: #7eca8f;--bs-form-valid-border-color: #7eca8f;--bs-form-invalid-color: #ea868f;--bs-form-invalid-border-color: #ea868f}*,*::before,*::after{box-sizing:border-box}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}h1,.h1,h2,.h2,h3,.h3,h4,.h4,h5,.h5,h6,.h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width: 1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (min-width: 1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (min-width: 1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (min-width: 1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:none}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#212529;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role="button"]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator{display:none !important}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-sm,.container-md,.container-lg,.container-xl,.container-xxl{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-sm,.container-md{max-width:720px}}@media (min-width: 992px){.container,.container-sm,.container-md,.container-lg{max-width:960px}}@media (min-width: 1200px){.container,.container-sm,.container-md,.container-lg,.container-xl{max-width:1140px}}@media (min-width: 1400px){.container,.container-sm,.container-md,.container-lg,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.row{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.333333%}.col-2{flex:0 0 auto;width:16.666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.333333%}.col-5{flex:0 0 auto;width:41.666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.333333%}.col-8{flex:0 0 auto;width:66.666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.333333%}.col-11{flex:0 0 auto;width:91.666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.333333%}.col-sm-2{flex:0 0 auto;width:16.666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.333333%}.col-sm-5{flex:0 0 auto;width:41.666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.333333%}.col-sm-8{flex:0 0 auto;width:66.666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.333333%}.col-sm-11{flex:0 0 auto;width:91.666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.333333%}.col-md-2{flex:0 0 auto;width:16.666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.333333%}.col-md-5{flex:0 0 auto;width:41.666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.333333%}.col-md-8{flex:0 0 auto;width:66.666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.333333%}.col-md-11{flex:0 0 auto;width:91.666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.333333%}.col-lg-2{flex:0 0 auto;width:16.666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.333333%}.col-lg-5{flex:0 0 auto;width:41.666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.333333%}.col-lg-8{flex:0 0 auto;width:66.666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.333333%}.col-lg-11{flex:0 0 auto;width:91.666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.333333%}.col-xl-2{flex:0 0 auto;width:16.666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.333333%}.col-xl-5{flex:0 0 auto;width:41.666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.333333%}.col-xl-8{flex:0 0 auto;width:66.666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.333333%}.col-xl-11{flex:0 0 auto;width:91.666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.333333%}.col-xxl-2{flex:0 0 auto;width:16.666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.333333%}.col-xxl-5{flex:0 0 auto;width:41.666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.333333%}.col-xxl-8{flex:0 0 auto;width:66.666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.333333%}.col-xxl-11{flex:0 0 auto;width:91.666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.333333%}.offset-xxl-2{margin-left:16.666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.333333%}.offset-xxl-5{margin-left:41.666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.333333%}.offset-xxl-8{margin-left:66.666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.333333%}.offset-xxl-11{margin-left:91.666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: var(--bs-emphasis-color);--bs-table-bg: rgba(0,0,0,0);--bs-table-border-color: var(--bs-border-color);--bs-table-accent-bg: rgba(0,0,0,0);--bs-table-striped-color: var(--bs-emphasis-color);--bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color: var(--bs-emphasis-color);--bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color: var(--bs-emphasis-color);--bs-table-hover-bg: #ced4da;width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #d4edda;--bs-table-border-color: #aabeae;--bs-table-striped-bg: #c9e1cf;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd5c4;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dbca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #e2e3e5;--bs-table-border-color: #b5b6b7;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d4edda;--bs-table-border-color: #aabeae;--bs-table-striped-bg: #c9e1cf;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd5c4;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dbca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #cff4fc;--bs-table-border-color: #a6c3ca;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #fff3cd;--bs-table-border-color: #ccc2a4;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #f8d7da;--bs-table-border-color: #c6acae;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #c6c7c8;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #343a40;--bs-table-border-color: #5d6166;--bs-table-striped-bg: #3e444a;--bs-table-striped-color: #fff;--bs-table-active-bg: #484e53;--bs-table-active-color: #fff;--bs-table-hover-bg: #43494e;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type="file"]{overflow:hidden}.form-control[type="file"]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#94d3a2;outline:0;box-shadow:none}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:-ms-input-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#94d3a2;outline:0;box-shadow:none}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme="dark"] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23f8f9fa' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg: var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);print-color-adjust:exact}.form-check-input[type="checkbox"]{border-radius:.25em}.form-check-input[type="radio"]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#94d3a2;outline:0;box-shadow:none}.form-check-input:checked{background-color:#28a745;border-color:#28a745}.form-check-input:checked[type="checkbox"]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type="radio"]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type="checkbox"]:indeterminate{background-color:#28a745;border-color:#28a745;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled] ~ .form-check-label,.form-check-input:disabled ~ .form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280,0,0,0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2394d3a2'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme="dark"] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255,255,255,0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:none}.form-range:focus::-moz-range-thumb{box-shadow:none}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#28a745;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bfe5c7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#28a745;border:0;border-radius:1rem;-moz-transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#bfe5c7}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity 0.1s ease-in-out,transform 0.1s ease-in-out}@media (prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder, .form-floating>.form-control-plaintext::-moz-placeholder{color:transparent}.form-floating>.form-control:-ms-input-placeholder, .form-floating>.form-control-plaintext:-ms-input-placeholder{color:transparent}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown), .form-floating>.form-control-plaintext:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-ms-input-placeholder), .form-floating>.form-control-plaintext:not(:-ms-input-placeholder){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown) ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:not(:-ms-input-placeholder) ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus ~ label,.form-floating>.form-control:not(:placeholder-shown) ~ label,.form-floating>.form-control-plaintext ~ label,.form-floating>.form-select ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown) ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:not(:-ms-input-placeholder) ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:focus ~ label::after,.form-floating>.form-control:not(:placeholder-shown) ~ label::after,.form-floating>.form-control-plaintext ~ label::after,.form-floating>.form-select ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext ~ label{border-width:var(--bs-border-width) 0}.form-floating>:disabled ~ label,.form-floating>.form-control:disabled ~ label{color:#6c757d}.form-floating>:disabled ~ label::after,.form-floating>.form-control:disabled ~ label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.was-validated :valid ~ .valid-feedback,.was-validated :valid ~ .valid-tooltip,.is-valid ~ .valid-feedback,.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:none;background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:none}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: none;padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:none}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + .75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:none}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input ~ .valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.was-validated :invalid ~ .invalid-feedback,.was-validated :invalid ~ .invalid-tooltip,.is-invalid ~ .invalid-feedback,.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:none;background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:none}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: none;padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:none}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + .75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:none}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input ~ .invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: .75rem;--bs-btn-padding-y: .375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: var(--bs-body-color);--bs-btn-bg: transparent;--bs-btn-border-width: var(--bs-border-width);--bs-btn-border-color: transparent;--bs-btn-border-radius: var(--bs-border-radius);--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: none;--bs-btn-disabled-opacity: .65;--bs-btn-focus-box-shadow: 0 0 0 0 rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6c757d;--bs-btn-hover-border-color: #6c757d;--bs-btn-focus-shadow-rgb: 130,138,145;--bs-btn-active-color: #fff;--bs-btn-active-bg: #565e64;--bs-btn-active-border-color: #51585e;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #6c757d;--bs-btn-disabled-border-color: #6c757d}.btn-success{--bs-btn-color: #000;--bs-btn-bg: #28a745;--bs-btn-border-color: #28a745;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #28a745;--bs-btn-hover-border-color: #28a745;--bs-btn-focus-shadow-rgb: 34,142,59;--bs-btn-active-color: #000;--bs-btn-active-bg: #53b96a;--bs-btn-active-border-color: #3eb058;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #28a745;--bs-btn-disabled-border-color: #28a745}.btn-warning{--bs-btn-color: #000;--bs-btn-bg: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 217,164,6;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffcd39;--bs-btn-active-border-color: #ffc720;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #ffc107;--bs-btn-disabled-border-color: #ffc107}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #dc3545;--bs-btn-hover-border-color: #dc3545;--bs-btn-focus-shadow-rgb: 225,83,97;--bs-btn-active-color: #fff;--bs-btn-active-bg: #b02a37;--bs-btn-active-border-color: #a52834;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #dc3545;--bs-btn-disabled-border-color: #dc3545}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 211,212,213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 82,88,93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-outline-secondary{--bs-btn-color: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6c757d;--bs-btn-hover-border-color: #6c757d;--bs-btn-focus-shadow-rgb: 108,117,125;--bs-btn-active-color: #fff;--bs-btn-active-bg: #6c757d;--bs-btn-active-border-color: #6c757d;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #6c757d;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #28a745;--bs-btn-border-color: #28a745;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #28a745;--bs-btn-hover-border-color: #28a745;--bs-btn-focus-shadow-rgb: 40,167,69;--bs-btn-active-color: #000;--bs-btn-active-bg: #28a745;--bs-btn-active-border-color: #28a745;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #28a745;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #28a745;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 255,193,7;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffc107;--bs-btn-active-border-color: #ffc107;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #ffc107;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ffc107;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #dc3545;--bs-btn-hover-border-color: #dc3545;--bs-btn-focus-shadow-rgb: 220,53,69;--bs-btn-active-color: #fff;--bs-btn-active-bg: #dc3545;--bs-btn-active-border-color: #dc3545;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #dc3545;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #dc3545;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248,249,250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52,58,64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: var(--bs-link-color);--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: var(--bs-link-hover-color);--bs-btn-hover-border-color: transparent;--bs-btn-active-color: var(--bs-link-hover-color);--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 66,70,73;text-decoration:none}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: .5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: .25rem;--bs-btn-padding-x: .5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius: var(--bs-border-radius-sm)}.fade{transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: .5rem;--bs-dropdown-spacer: .125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: var(--bs-body-color);--bs-dropdown-bg: var(--bs-body-bg);--bs-dropdown-border-color: var(--bs-border-color-translucent);--bs-dropdown-border-radius: var(--bs-border-radius);--bs-dropdown-border-width: var(--bs-border-width);--bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg: var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y: .5rem;--bs-dropdown-box-shadow: var(--bs-box-shadow);--bs-dropdown-link-color: var(--bs-body-color);--bs-dropdown-link-hover-color: var(--bs-body-color);--bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #28a745;--bs-dropdown-link-disabled-color: var(--bs-tertiary-color);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: .25rem;--bs-dropdown-header-color: #6c757d;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: .5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius, 0)}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #343a40;--bs-dropdown-border-color: var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg: rgba(255,255,255,0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #28a745;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn ~ .btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: .5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-link-color);--bs-nav-link-hover-color: var(--bs-link-hover-color);--bs-nav-link-disabled-color: var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:none;border:0;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(40,167,69,0.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: var(--bs-border-width);--bs-nav-tabs-border-color: var(--bs-border-color);--bs-nav-tabs-border-radius: var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color: var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg: var(--bs-body-bg);--bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius: var(--bs-border-radius);--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #28a745}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: .125rem;--bs-nav-underline-link-active-color: var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: .5rem;--bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y: .3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x: .5rem;--bs-navbar-toggler-padding-y: .25rem;--bs-navbar-toggler-padding-x: .75rem;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833,37,41,0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius: var(--bs-border-radius);--bs-navbar-toggler-focus-width: 0;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: .5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media (min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme="dark"]{--bs-navbar-color: rgba(255,255,255,0.55);--bs-navbar-hover-color: rgba(255,255,255,0.75);--bs-navbar-disabled-color: rgba(255,255,255,0.25);--bs-navbar-active-color: #fff;--bs-navbar-brand-color: #fff;--bs-navbar-brand-hover-color: #fff;--bs-navbar-toggler-border-color: rgba(255,255,255,0.1);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255,255,255,0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme="dark"] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255,255,255,0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: .5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 0;--bs-card-border-color: rgba(0,0,0,0);--bs-card-border-radius: var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(var(--bs-border-radius) - 0);--bs-card-cap-padding-y: .5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(248,249,250,0.1);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: rgba(0,0,0,0);--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: .75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width: 576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: #e9ecef;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: var(--bs-secondary-color);--bs-breadcrumb-item-padding-x: .5rem;--bs-breadcrumb-item-active-color: var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: .75rem;--bs-pagination-padding-y: .375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: var(--bs-link-color);--bs-pagination-bg: var(--bs-body-bg);--bs-pagination-border-width: var(--bs-border-width);--bs-pagination-border-color: var(--bs-border-color);--bs-pagination-border-radius: var(--bs-border-radius);--bs-pagination-hover-color: var(--bs-link-hover-color);--bs-pagination-hover-bg: var(--bs-tertiary-bg);--bs-pagination-hover-border-color: var(--bs-border-color);--bs-pagination-focus-color: var(--bs-link-hover-color);--bs-pagination-focus-bg: var(--bs-secondary-bg);--bs-pagination-focus-box-shadow: 0 0 0 .25rem rgba(40,167,69,0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #28a745;--bs-pagination-active-border-color: #28a745;--bs-pagination-disabled-color: var(--bs-secondary-color);--bs-pagination-disabled-bg: var(--bs-secondary-bg);--bs-pagination-disabled-border-color: var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: .75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x: .5rem;--bs-pagination-padding-y: .25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius: var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x: .65em;--bs-badge-padding-y: .35em;--bs-badge-font-size:.75em;--bs-badge-font-weight: 700;--bs-badge-color: #fff;--bs-badge-border-radius: var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius: var(--bs-border-radius);--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height: 1rem;--bs-progress-font-size:.75rem;--bs-progress-bg: var(--bs-secondary-bg);--bs-progress-border-radius: var(--bs-border-radius);--bs-progress-box-shadow: var(--bs-box-shadow-inset);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #28a745;--bs-progress-bar-transition: width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion: reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{--bs-list-group-color: var(--bs-body-color);--bs-list-group-bg: var(--bs-body-bg);--bs-list-group-border-color: var(--bs-border-color);--bs-list-group-border-width: var(--bs-border-width);--bs-list-group-border-radius: var(--bs-border-radius);--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: .5rem;--bs-list-group-action-color: var(--bs-secondary-color);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-tertiary-bg);--bs-list-group-action-active-color: var(--bs-body-color);--bs-list-group-action-active-bg: var(--bs-secondary-bg);--bs-list-group-disabled-color: var(--bs-secondary-color);--bs-list-group-disabled-bg: var(--bs-body-bg);--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #28a745;--bs-list-group-active-border-color: #28a745;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width: 576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: .5;--bs-btn-close-hover-opacity: .75;--bs-btn-close-focus-shadow: none;--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: .25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme="dark"] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: .75rem;--bs-toast-padding-y: .5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width: var(--bs-border-width);--bs-toast-border-color: var(--bs-border-color-translucent);--bs-toast-border-radius: var(--bs-border-radius);--bs-toast-box-shadow: var(--bs-box-shadow);--bs-toast-header-color: var(--bs-secondary-color);--bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color: none;width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: .5rem;--bs-modal-color: ;--bs-modal-bg: var(--bs-body-bg);--bs-modal-border-color: var(--bs-border-color-translucent);--bs-modal-border-width: var(--bs-border-width);--bs-modal-border-radius: var(--bs-border-radius-lg);--bs-modal-box-shadow: var(--bs-box-shadow-sm);--bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: var(--bs-border-color);--bs-modal-header-border-width: var(--bs-border-width);--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: .5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: var(--bs-border-color);--bs-modal-footer-border-width: var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform 0.3s ease-out;transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: .5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media (min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media (min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: none;--bs-popover-font-size:.875rem;--bs-popover-bg: var(--bs-body-bg);--bs-popover-border-width: var(--bs-border-width);--bs-popover-border-color: var(--bs-border-color-translucent);--bs-popover-border-radius: var(--bs-border-radius-lg);--bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow: var(--bs-box-shadow);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: .5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: var(--bs-secondary-bg);--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: var(--bs-body-color);--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: .5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme="dark"] .carousel .carousel-control-prev-icon,[data-bs-theme="dark"] .carousel .carousel-control-next-icon,[data-bs-theme="dark"].carousel .carousel-control-prev-icon,[data-bs-theme="dark"].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme="dark"] .carousel .carousel-indicators [data-bs-target],[data-bs-theme="dark"].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme="dark"] .carousel .carousel-caption,[data-bs-theme="dark"].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;-webkit-animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@-webkit-keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -.125em;--bs-spinner-border-width: .25em;--bs-spinner-animation-speed: .75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: .2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -.125em;--bs-spinner-animation-speed: .75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media (prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas-sm,.offcanvas-md,.offcanvas-lg,.offcanvas-xl,.offcanvas-xxl,.offcanvas{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 0;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: var(--bs-body-color);--bs-offcanvas-bg: var(--bs-body-bg);--bs-offcanvas-border-width: var(--bs-border-width);--bs-offcanvas-border-color: var(--bs-border-color-translucent);--bs-offcanvas-box-shadow: var(--bs-box-shadow-sm);--bs-offcanvas-transition: transform .3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media (max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 575.98px) and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media (max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 767.98px) and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media (max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 991.98px) and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media (max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media (max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media (max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.clearfix::after{display:block;clear:both;content:""}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#000 !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#000 !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86,94,100, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(86,94,100, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(86,94,100, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(83,185,106, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(83,185,106, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(83,185,106, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(255,205,57, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(255,205,57, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(255,205,57, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(176,42,55, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(176,42,55, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(176,42,55, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249,250,251, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(249,250,251, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(249,250,251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(42,46,51, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(42,46,51, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(42,46,51, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:0.2s ease-in-out transform}@media (prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-none{-o-object-fit:none !important;object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:var(--bs-box-shadow) !important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm) !important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg) !important}.shadow-none{box-shadow:none !important}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:-webkit-sticky !important;position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: .1}.border-opacity-25{--bs-border-opacity: .25}.border-opacity-50{--bs-border-opacity: .5}.border-opacity-75{--bs-border-opacity: .75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.375rem + 1.5vw) !important}.fs-2{font-size:calc(1.325rem + .9vw) !important}.fs-3{font-size:calc(1.3rem + .6vw) !important}.fs-4{font-size:calc(1.275rem + .3vw) !important}.fs-5{font-size:1.25rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,0.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,0.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: .25}.text-opacity-50{--bs-text-opacity: .5}.text-opacity-75{--bs-text-opacity: .75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: .1}.link-opacity-10-hover:hover{--bs-link-opacity: .1}.link-opacity-25{--bs-link-opacity: .25}.link-opacity-25-hover:hover{--bs-link-opacity: .25}.link-opacity-50{--bs-link-opacity: .5}.link-opacity-50-hover:hover{--bs-link-opacity: .5}.link-opacity-75{--bs-link-opacity: .75}.link-opacity-75-hover:hover{--bs-link-opacity: .75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-secondary{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: .1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: .1}.link-underline-opacity-25{--bs-link-underline-opacity: .25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: .25}.link-underline-opacity-50{--bs-link-underline-opacity: .5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: .5}.link-underline-opacity-75{--bs-link-underline-opacity: .75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: .75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: .1}.bg-opacity-25{--bs-bg-opacity: .25}.bg-opacity-50{--bs-bg-opacity: .5}.bg-opacity-75{--bs-bg-opacity: .75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{-webkit-user-select:all !important;-moz-user-select:all !important;user-select:all !important}.user-select-auto{-webkit-user-select:auto !important;-moz-user-select:auto !important;-ms-user-select:auto !important;user-select:auto !important}.user-select-none{-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media (min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-sm-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-sm-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-sm-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-sm-none{-o-object-fit:none !important;object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-sm-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-sm-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-sm-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-sm-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-sm-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-md-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-md-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-md-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-md-none{-o-object-fit:none !important;object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-md-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-md-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-md-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-md-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-md-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-lg-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-lg-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-lg-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-lg-none{-o-object-fit:none !important;object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-lg-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-lg-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-lg-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-lg-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-lg-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-xl-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-xl-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-xl-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-xl-none{-o-object-fit:none !important;object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-xl-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-xl-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-xl-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-xl-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-xl-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media (min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-xxl-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-xxl-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-xxl-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-xxl-none{-o-object-fit:none !important;object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-xxl-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-xxl-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-xxl-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-xxl-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-xxl-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem !important}.fs-2{font-size:2rem !important}.fs-3{font-size:1.75rem !important}.fs-4{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}} +:root,[data-bs-theme="light"]{--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #28a745;--bs-black: #000;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-secondary: #6c757d;--bs-success: #28a745;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #343a40;--bs-secondary-rgb: 108,117,125;--bs-success-rgb: 40,167,69;--bs-warning-rgb: 255,193,7;--bs-danger-rgb: 220,53,69;--bs-light-rgb: 248,249,250;--bs-dark-rgb: 52,58,64;--bs-primary-text-emphasis: #10431c;--bs-secondary-text-emphasis: #2b2f32;--bs-success-text-emphasis: #10431c;--bs-info-text-emphasis: #055160;--bs-warning-text-emphasis: #664d03;--bs-danger-text-emphasis: #58151c;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4edda;--bs-secondary-bg-subtle: #e2e3e5;--bs-success-bg-subtle: #d4edda;--bs-info-bg-subtle: #cff4fc;--bs-warning-bg-subtle: #fff3cd;--bs-danger-bg-subtle: #f8d7da;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9dcb5;--bs-secondary-border-subtle: #c4c8cb;--bs-success-border-subtle: #a9dcb5;--bs-info-border-subtle: #9eeaf9;--bs-warning-border-subtle: #ffe69c;--bs-danger-border-subtle: #f1aeb5;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255,255,255;--bs-black-rgb: 0,0,0;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255,255,255,0.15), rgba(255,255,255,0));--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-color-rgb: 33,37,41;--bs-body-bg: #f8f9fa;--bs-body-bg-rgb: 248,249,250;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0,0,0;--bs-secondary-color: rgba(33,37,41,0.75);--bs-secondary-color-rgb: 33,37,41;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233,236,239;--bs-tertiary-color: rgba(33,37,41,0.5);--bs-tertiary-color-rgb: 33,37,41;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248,249,250;--bs-heading-color: inherit;--bs-link-color: #212529;--bs-link-color-rgb: 33,37,41;--bs-link-decoration: none;--bs-link-hover-color: #1a1e21;--bs-link-hover-color-rgb: 26,30,33;--bs-code-color: #d63384;--bs-highlight-color: #212529;--bs-highlight-bg: #fff3cd;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #dee2e6;--bs-border-color-translucent: rgba(0,0,0,0.175);--bs-border-radius: .375rem;--bs-border-radius-sm: .25rem;--bs-border-radius-lg: .5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: none;--bs-box-shadow-sm: none;--bs-box-shadow-lg: none;--bs-box-shadow-inset: none;--bs-focus-ring-width: .25rem;--bs-focus-ring-opacity: .25;--bs-focus-ring-color: rgba(40,167,69,0.25);--bs-form-valid-color: #28a745;--bs-form-valid-border-color: #28a745;--bs-form-invalid-color: #dc3545;--bs-form-invalid-border-color: #dc3545}[data-bs-theme="dark"]{color-scheme:dark;--bs-body-color: #f8f9fa;--bs-body-color-rgb: 248,249,250;--bs-body-bg: #343a40;--bs-body-bg-rgb: 52,58,64;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255,255,255;--bs-secondary-color: rgba(248,249,250,0.75);--bs-secondary-color-rgb: 248,249,250;--bs-secondary-bg: #343a40;--bs-secondary-bg-rgb: 52,58,64;--bs-tertiary-color: rgba(248,249,250,0.5);--bs-tertiary-color-rgb: 248,249,250;--bs-tertiary-bg: #2b3035;--bs-tertiary-bg-rgb: 43,48,53;--bs-primary-text-emphasis: #7eca8f;--bs-secondary-text-emphasis: #a7acb1;--bs-success-text-emphasis: #208637;--bs-info-text-emphasis: #6edff6;--bs-warning-text-emphasis: #664d03;--bs-danger-text-emphasis: #b02a37;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #08210e;--bs-secondary-bg-subtle: #161719;--bs-success-bg-subtle: #d4edda;--bs-info-bg-subtle: #032830;--bs-warning-bg-subtle: #fff3cd;--bs-danger-bg-subtle: #f8d7da;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #186429;--bs-secondary-border-subtle: #41464b;--bs-success-border-subtle: #a9dcb5;--bs-info-border-subtle: #087990;--bs-warning-border-subtle: #ffe69c;--bs-danger-border-subtle: #f1aeb5;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #f8f9fa;--bs-link-hover-color: #f9fafb;--bs-link-color-rgb: 248,249,250;--bs-link-hover-color-rgb: 249,250,251;--bs-code-color: #e685b5;--bs-highlight-color: #f8f9fa;--bs-highlight-bg: #664d03;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255,255,255,0.15);--bs-form-valid-color: #7eca8f;--bs-form-valid-border-color: #7eca8f;--bs-form-invalid-color: #ea868f;--bs-form-invalid-border-color: #ea868f}*,*::before,*::after{box-sizing:border-box}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}h1,.h1,h2,.h2,h3,.h3,h4,.h4,h5,.h5,h6,.h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width: 1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (min-width: 1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (min-width: 1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (min-width: 1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:none}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#212529;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role="button"]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator{display:none !important}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-sm,.container-md,.container-lg,.container-xl,.container-xxl{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-sm,.container-md{max-width:720px}}@media (min-width: 992px){.container,.container-sm,.container-md,.container-lg{max-width:960px}}@media (min-width: 1200px){.container,.container-sm,.container-md,.container-lg,.container-xl{max-width:1140px}}@media (min-width: 1400px){.container,.container-sm,.container-md,.container-lg,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.row{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.333333%}.col-2{flex:0 0 auto;width:16.666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.333333%}.col-5{flex:0 0 auto;width:41.666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.333333%}.col-8{flex:0 0 auto;width:66.666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.333333%}.col-11{flex:0 0 auto;width:91.666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.333333%}.col-sm-2{flex:0 0 auto;width:16.666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.333333%}.col-sm-5{flex:0 0 auto;width:41.666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.333333%}.col-sm-8{flex:0 0 auto;width:66.666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.333333%}.col-sm-11{flex:0 0 auto;width:91.666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.333333%}.col-md-2{flex:0 0 auto;width:16.666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.333333%}.col-md-5{flex:0 0 auto;width:41.666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.333333%}.col-md-8{flex:0 0 auto;width:66.666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.333333%}.col-md-11{flex:0 0 auto;width:91.666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.333333%}.col-lg-2{flex:0 0 auto;width:16.666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.333333%}.col-lg-5{flex:0 0 auto;width:41.666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.333333%}.col-lg-8{flex:0 0 auto;width:66.666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.333333%}.col-lg-11{flex:0 0 auto;width:91.666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.333333%}.col-xl-2{flex:0 0 auto;width:16.666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.333333%}.col-xl-5{flex:0 0 auto;width:41.666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.333333%}.col-xl-8{flex:0 0 auto;width:66.666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.333333%}.col-xl-11{flex:0 0 auto;width:91.666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.333333%}.col-xxl-2{flex:0 0 auto;width:16.666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.333333%}.col-xxl-5{flex:0 0 auto;width:41.666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.333333%}.col-xxl-8{flex:0 0 auto;width:66.666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.333333%}.col-xxl-11{flex:0 0 auto;width:91.666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.333333%}.offset-xxl-2{margin-left:16.666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.333333%}.offset-xxl-5{margin-left:41.666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.333333%}.offset-xxl-8{margin-left:66.666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.333333%}.offset-xxl-11{margin-left:91.666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: var(--bs-emphasis-color);--bs-table-bg: rgba(0,0,0,0);--bs-table-border-color: var(--bs-border-color);--bs-table-accent-bg: rgba(0,0,0,0);--bs-table-striped-color: var(--bs-emphasis-color);--bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color: var(--bs-emphasis-color);--bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color: var(--bs-emphasis-color);--bs-table-hover-bg: #ced4da;width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #d4edda;--bs-table-border-color: #aabeae;--bs-table-striped-bg: #c9e1cf;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd5c4;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dbca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #e2e3e5;--bs-table-border-color: #b5b6b7;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d4edda;--bs-table-border-color: #aabeae;--bs-table-striped-bg: #c9e1cf;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd5c4;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dbca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #cff4fc;--bs-table-border-color: #a6c3ca;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #fff3cd;--bs-table-border-color: #ccc2a4;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #f8d7da;--bs-table-border-color: #c6acae;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #c6c7c8;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #343a40;--bs-table-border-color: #5d6166;--bs-table-striped-bg: #3e444a;--bs-table-striped-color: #fff;--bs-table-active-bg: #484e53;--bs-table-active-color: #fff;--bs-table-hover-bg: #43494e;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type="file"]{overflow:hidden}.form-control[type="file"]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#94d3a2;outline:0;box-shadow:none}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:-ms-input-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#94d3a2;outline:0;box-shadow:none}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme="dark"] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23f8f9fa' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg: var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);print-color-adjust:exact}.form-check-input[type="checkbox"]{border-radius:.25em}.form-check-input[type="radio"]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#94d3a2;outline:0;box-shadow:none}.form-check-input:checked{background-color:#28a745;border-color:#28a745}.form-check-input:checked[type="checkbox"]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type="radio"]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type="checkbox"]:indeterminate{background-color:#28a745;border-color:#28a745;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled] ~ .form-check-label,.form-check-input:disabled ~ .form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280,0,0,0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2394d3a2'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme="dark"] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255,255,255,0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:none}.form-range:focus::-moz-range-thumb{box-shadow:none}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#28a745;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bfe5c7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#28a745;border:0;border-radius:1rem;-moz-transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#bfe5c7}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity 0.1s ease-in-out,transform 0.1s ease-in-out}@media (prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder, .form-floating>.form-control-plaintext::-moz-placeholder{color:transparent}.form-floating>.form-control:-ms-input-placeholder, .form-floating>.form-control-plaintext:-ms-input-placeholder{color:transparent}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown), .form-floating>.form-control-plaintext:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-ms-input-placeholder), .form-floating>.form-control-plaintext:not(:-ms-input-placeholder){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown) ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:not(:-ms-input-placeholder) ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus ~ label,.form-floating>.form-control:not(:placeholder-shown) ~ label,.form-floating>.form-control-plaintext ~ label,.form-floating>.form-select ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown) ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:not(:-ms-input-placeholder) ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:focus ~ label::after,.form-floating>.form-control:not(:placeholder-shown) ~ label::after,.form-floating>.form-control-plaintext ~ label::after,.form-floating>.form-select ~ label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill ~ label{color:rgba(var(--bs-body-color-rgb), .65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext ~ label{border-width:var(--bs-border-width) 0}.form-floating>:disabled ~ label,.form-floating>.form-control:disabled ~ label{color:#6c757d}.form-floating>:disabled ~ label::after,.form-floating>.form-control:disabled ~ label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.was-validated :valid ~ .valid-feedback,.was-validated :valid ~ .valid-tooltip,.is-valid ~ .valid-feedback,.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:none;background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:none}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: none;padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:none}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + .75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:var(--bs-form-valid-border-color)}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:var(--bs-form-valid-color)}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:none}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input ~ .valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.was-validated :invalid ~ .invalid-feedback,.was-validated :invalid ~ .invalid-tooltip,.is-invalid ~ .invalid-feedback,.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:none;background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:none}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: none;padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:none}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + .75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:var(--bs-form-invalid-border-color)}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:var(--bs-form-invalid-color)}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:none}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input ~ .invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: .75rem;--bs-btn-padding-y: .375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: var(--bs-body-color);--bs-btn-bg: transparent;--bs-btn-border-width: var(--bs-border-width);--bs-btn-border-color: transparent;--bs-btn-border-radius: var(--bs-border-radius);--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: none;--bs-btn-disabled-opacity: .65;--bs-btn-focus-box-shadow: 0 0 0 0 rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6c757d;--bs-btn-hover-border-color: #6c757d;--bs-btn-focus-shadow-rgb: 130,138,145;--bs-btn-active-color: #fff;--bs-btn-active-bg: #565e64;--bs-btn-active-border-color: #51585e;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #6c757d;--bs-btn-disabled-border-color: #6c757d}.btn-success{--bs-btn-color: #000;--bs-btn-bg: #28a745;--bs-btn-border-color: #28a745;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #28a745;--bs-btn-hover-border-color: #28a745;--bs-btn-focus-shadow-rgb: 34,142,59;--bs-btn-active-color: #000;--bs-btn-active-bg: #53b96a;--bs-btn-active-border-color: #3eb058;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #28a745;--bs-btn-disabled-border-color: #28a745}.btn-warning{--bs-btn-color: #000;--bs-btn-bg: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 217,164,6;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffcd39;--bs-btn-active-border-color: #ffc720;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #ffc107;--bs-btn-disabled-border-color: #ffc107}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #dc3545;--bs-btn-hover-border-color: #dc3545;--bs-btn-focus-shadow-rgb: 225,83,97;--bs-btn-active-color: #fff;--bs-btn-active-bg: #b02a37;--bs-btn-active-border-color: #a52834;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #dc3545;--bs-btn-disabled-border-color: #dc3545}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 211,212,213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 82,88,93;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5d6166;--bs-btn-active-border-color: #484e53;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #343a40;--bs-btn-disabled-border-color: #343a40}.btn-outline-secondary{--bs-btn-color: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #6c757d;--bs-btn-hover-border-color: #6c757d;--bs-btn-focus-shadow-rgb: 108,117,125;--bs-btn-active-color: #fff;--bs-btn-active-bg: #6c757d;--bs-btn-active-border-color: #6c757d;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #6c757d;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #28a745;--bs-btn-border-color: #28a745;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #28a745;--bs-btn-hover-border-color: #28a745;--bs-btn-focus-shadow-rgb: 40,167,69;--bs-btn-active-color: #000;--bs-btn-active-bg: #28a745;--bs-btn-active-border-color: #28a745;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #28a745;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #28a745;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 255,193,7;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffc107;--bs-btn-active-border-color: #ffc107;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #ffc107;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ffc107;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #dc3545;--bs-btn-hover-border-color: #dc3545;--bs-btn-focus-shadow-rgb: 220,53,69;--bs-btn-active-color: #fff;--bs-btn-active-bg: #dc3545;--bs-btn-active-border-color: #dc3545;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #dc3545;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #dc3545;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248,249,250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #343a40;--bs-btn-border-color: #343a40;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #343a40;--bs-btn-hover-border-color: #343a40;--bs-btn-focus-shadow-rgb: 52,58,64;--bs-btn-active-color: #fff;--bs-btn-active-bg: #343a40;--bs-btn-active-border-color: #343a40;--bs-btn-active-shadow: none;--bs-btn-disabled-color: #343a40;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #343a40;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: var(--bs-link-color);--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: var(--bs-link-hover-color);--bs-btn-hover-border-color: transparent;--bs-btn-active-color: var(--bs-link-hover-color);--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 66,70,73;text-decoration:none}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: .5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: var(--bs-border-radius-lg)}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: .25rem;--bs-btn-padding-x: .5rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius: var(--bs-border-radius-sm)}.fade{transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: .5rem;--bs-dropdown-spacer: .125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: var(--bs-body-color);--bs-dropdown-bg: var(--bs-body-bg);--bs-dropdown-border-color: var(--bs-border-color-translucent);--bs-dropdown-border-radius: var(--bs-border-radius);--bs-dropdown-border-width: var(--bs-border-width);--bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg: var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y: .5rem;--bs-dropdown-box-shadow: var(--bs-box-shadow);--bs-dropdown-link-color: var(--bs-body-color);--bs-dropdown-link-hover-color: var(--bs-body-color);--bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #28a745;--bs-dropdown-link-disabled-color: var(--bs-tertiary-color);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: .25rem;--bs-dropdown-header-color: #6c757d;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: .5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius, 0)}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #343a40;--bs-dropdown-border-color: var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg: rgba(255,255,255,0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #28a745;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn ~ .btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: .5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-link-color);--bs-nav-link-hover-color: var(--bs-link-hover-color);--bs-nav-link-disabled-color: var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);background:none;border:0;transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(40,167,69,0.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: var(--bs-border-width);--bs-nav-tabs-border-color: var(--bs-border-color);--bs-nav-tabs-border-radius: var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color: var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg: var(--bs-body-bg);--bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius: var(--bs-border-radius);--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #28a745}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: .125rem;--bs-nav-underline-link-active-color: var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: .5rem;--bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y: .3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x: .5rem;--bs-navbar-toggler-padding-y: .25rem;--bs-navbar-toggler-padding-x: .75rem;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833,37,41,0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius: var(--bs-border-radius);--bs-navbar-toggler-focus-width: 0;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: .5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media (min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:transparent !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme="dark"]{--bs-navbar-color: rgba(255,255,255,0.55);--bs-navbar-hover-color: rgba(255,255,255,0.75);--bs-navbar-disabled-color: rgba(255,255,255,0.25);--bs-navbar-active-color: #fff;--bs-navbar-brand-color: #fff;--bs-navbar-brand-hover-color: #fff;--bs-navbar-toggler-border-color: rgba(255,255,255,0.1);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255,255,255,0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme="dark"] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255,255,255,0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: .5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 0;--bs-card-border-color: rgba(0,0,0,0);--bs-card-border-radius: var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(var(--bs-border-radius) - 0);--bs-card-cap-padding-y: .5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(248,249,250,0.1);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: rgba(0,0,0,0);--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: .75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width: 576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: #e9ecef;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: var(--bs-secondary-color);--bs-breadcrumb-item-padding-x: .5rem;--bs-breadcrumb-item-active-color: var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: .75rem;--bs-pagination-padding-y: .375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: var(--bs-link-color);--bs-pagination-bg: var(--bs-body-bg);--bs-pagination-border-width: var(--bs-border-width);--bs-pagination-border-color: var(--bs-border-color);--bs-pagination-border-radius: var(--bs-border-radius);--bs-pagination-hover-color: var(--bs-link-hover-color);--bs-pagination-hover-bg: var(--bs-tertiary-bg);--bs-pagination-hover-border-color: var(--bs-border-color);--bs-pagination-focus-color: var(--bs-link-hover-color);--bs-pagination-focus-bg: var(--bs-secondary-bg);--bs-pagination-focus-box-shadow: 0 0 0 .25rem rgba(40,167,69,0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #28a745;--bs-pagination-active-border-color: #28a745;--bs-pagination-disabled-color: var(--bs-secondary-color);--bs-pagination-disabled-bg: var(--bs-secondary-bg);--bs-pagination-disabled-border-color: var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: .75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x: .5rem;--bs-pagination-padding-y: .25rem;--bs-pagination-font-size:.875rem;--bs-pagination-border-radius: var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x: .65em;--bs-badge-padding-y: .35em;--bs-badge-font-size:.75em;--bs-badge-font-weight: 700;--bs-badge-color: #fff;--bs-badge-border-radius: var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius: var(--bs-border-radius);--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height: 1rem;--bs-progress-font-size:.75rem;--bs-progress-bg: var(--bs-secondary-bg);--bs-progress-border-radius: var(--bs-border-radius);--bs-progress-box-shadow: var(--bs-box-shadow-inset);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #28a745;--bs-progress-bar-transition: width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion: reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{--bs-list-group-color: var(--bs-body-color);--bs-list-group-bg: var(--bs-body-bg);--bs-list-group-border-color: var(--bs-border-color);--bs-list-group-border-width: var(--bs-border-width);--bs-list-group-border-radius: var(--bs-border-radius);--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: .5rem;--bs-list-group-action-color: var(--bs-secondary-color);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-tertiary-bg);--bs-list-group-action-active-color: var(--bs-body-color);--bs-list-group-action-active-bg: var(--bs-secondary-bg);--bs-list-group-disabled-color: var(--bs-secondary-color);--bs-list-group-disabled-bg: var(--bs-body-bg);--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #28a745;--bs-list-group-active-border-color: #28a745;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width: 576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: .5;--bs-btn-close-hover-opacity: .75;--bs-btn-close-focus-shadow: none;--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: .25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme="dark"] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: .75rem;--bs-toast-padding-y: .5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width: var(--bs-border-width);--bs-toast-border-color: var(--bs-border-color-translucent);--bs-toast-border-radius: var(--bs-border-radius);--bs-toast-box-shadow: var(--bs-box-shadow);--bs-toast-header-color: var(--bs-secondary-color);--bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color: none;width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: .5rem;--bs-modal-color: ;--bs-modal-bg: var(--bs-body-bg);--bs-modal-border-color: var(--bs-border-color-translucent);--bs-modal-border-width: var(--bs-border-width);--bs-modal-border-radius: var(--bs-border-radius-lg);--bs-modal-box-shadow: var(--bs-box-shadow-sm);--bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: var(--bs-border-color);--bs-modal-header-border-width: var(--bs-border-width);--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: .5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: var(--bs-border-color);--bs-modal-footer-border-width: var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform 0.3s ease-out;transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: .5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media (min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media (min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: none;--bs-popover-font-size:.875rem;--bs-popover-bg: var(--bs-body-bg);--bs-popover-border-width: var(--bs-border-width);--bs-popover-border-color: var(--bs-border-color-translucent);--bs-popover-border-radius: var(--bs-border-radius-lg);--bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow: var(--bs-box-shadow);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: .5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: var(--bs-secondary-bg);--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: var(--bs-body-color);--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: .5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="top"]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="right"]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="bottom"]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^="left"]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme="dark"] .carousel .carousel-control-prev-icon,[data-bs-theme="dark"] .carousel .carousel-control-next-icon,[data-bs-theme="dark"].carousel .carousel-control-prev-icon,[data-bs-theme="dark"].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme="dark"] .carousel .carousel-indicators [data-bs-target],[data-bs-theme="dark"].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme="dark"] .carousel .carousel-caption,[data-bs-theme="dark"].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;-webkit-animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@-webkit-keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -.125em;--bs-spinner-border-width: .25em;--bs-spinner-animation-speed: .75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: .2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -.125em;--bs-spinner-animation-speed: .75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media (prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas-sm,.offcanvas-md,.offcanvas-lg,.offcanvas-xl,.offcanvas-xxl,.offcanvas{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 1rem;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: var(--bs-body-color);--bs-offcanvas-bg: var(--bs-body-bg);--bs-offcanvas-border-width: var(--bs-border-width);--bs-offcanvas-border-color: var(--bs-border-color-translucent);--bs-offcanvas-box-shadow: var(--bs-box-shadow-sm);--bs-offcanvas-transition: transform .3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media (max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 575.98px) and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media (max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media (min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 767.98px) and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media (max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media (min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 991.98px) and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media (max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media (min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media (max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media (min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}@media (max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media (max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media (min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:transparent !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin:calc(-.5 * var(--bs-offcanvas-padding-y)) calc(-.5 * var(--bs-offcanvas-padding-x)) calc(-.5 * var(--bs-offcanvas-padding-y)) auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.clearfix::after{display:block;clear:both;content:""}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#000 !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#000 !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86,94,100, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(86,94,100, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(86,94,100, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(83,185,106, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(83,185,106, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(83,185,106, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(255,205,57, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(255,205,57, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(255,205,57, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(176,42,55, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(176,42,55, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(176,42,55, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249,250,251, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(249,250,251, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(249,250,251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(42,46,51, var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(42,46,51, var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(42,46,51, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:0.2s ease-in-out transform}@media (prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-none{-o-object-fit:none !important;object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:var(--bs-box-shadow) !important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm) !important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg) !important}.shadow-none{box-shadow:none !important}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:-webkit-sticky !important;position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: .1}.border-opacity-25{--bs-border-opacity: .25}.border-opacity-50{--bs-border-opacity: .5}.border-opacity-75{--bs-border-opacity: .75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.375rem + 1.5vw) !important}.fs-2{font-size:calc(1.325rem + .9vw) !important}.fs-3{font-size:calc(1.3rem + .6vw) !important}.fs-4{font-size:calc(1.275rem + .3vw) !important}.fs-5{font-size:1.25rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,0.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,0.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: .25}.text-opacity-50{--bs-text-opacity: .5}.text-opacity-75{--bs-text-opacity: .75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: .1}.link-opacity-10-hover:hover{--bs-link-opacity: .1}.link-opacity-25{--bs-link-opacity: .25}.link-opacity-25-hover:hover{--bs-link-opacity: .25}.link-opacity-50{--bs-link-opacity: .5}.link-opacity-50-hover:hover{--bs-link-opacity: .5}.link-opacity-75{--bs-link-opacity: .75}.link-opacity-75-hover:hover{--bs-link-opacity: .75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-secondary{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: .1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: .1}.link-underline-opacity-25{--bs-link-underline-opacity: .25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: .25}.link-underline-opacity-50{--bs-link-underline-opacity: .5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: .5}.link-underline-opacity-75{--bs-link-underline-opacity: .75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: .75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: .1}.bg-opacity-25{--bs-bg-opacity: .25}.bg-opacity-50{--bs-bg-opacity: .5}.bg-opacity-75{--bs-bg-opacity: .75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{-webkit-user-select:all !important;-moz-user-select:all !important;user-select:all !important}.user-select-auto{-webkit-user-select:auto !important;-moz-user-select:auto !important;-ms-user-select:auto !important;user-select:auto !important}.user-select-none{-webkit-user-select:none !important;-moz-user-select:none !important;-ms-user-select:none !important;user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media (min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-sm-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-sm-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-sm-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-sm-none{-o-object-fit:none !important;object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-sm-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-sm-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-sm-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-sm-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-sm-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-md-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-md-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-md-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-md-none{-o-object-fit:none !important;object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-md-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-md-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-md-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-md-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-md-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-lg-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-lg-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-lg-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-lg-none{-o-object-fit:none !important;object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-lg-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-lg-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-lg-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-lg-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-lg-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-xl-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-xl-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-xl-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-xl-none{-o-object-fit:none !important;object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-xl-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-xl-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-xl-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-xl-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-xl-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media (min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{-o-object-fit:contain !important;object-fit:contain !important}.object-fit-xxl-cover{-o-object-fit:cover !important;object-fit:cover !important}.object-fit-xxl-fill{-o-object-fit:fill !important;object-fit:fill !important}.object-fit-xxl-scale{-o-object-fit:scale-down !important;object-fit:scale-down !important}.object-fit-xxl-none{-o-object-fit:none !important;object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{-moz-column-gap:0 !important;column-gap:0 !important}.column-gap-xxl-1{-moz-column-gap:.25rem !important;column-gap:.25rem !important}.column-gap-xxl-2{-moz-column-gap:.5rem !important;column-gap:.5rem !important}.column-gap-xxl-3{-moz-column-gap:1rem !important;column-gap:1rem !important}.column-gap-xxl-4{-moz-column-gap:1.5rem !important;column-gap:1.5rem !important}.column-gap-xxl-5{-moz-column-gap:3rem !important;column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem !important}.fs-2{font-size:2rem !important}.fs-3{font-size:1.75rem !important}.fs-4{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}} diff --git a/dist/bootstrap/package.json b/dist/bootstrap/package.json index d487d2144..fcb4870b4 100644 --- a/dist/bootstrap/package.json +++ b/dist/bootstrap/package.json @@ -16,7 +16,7 @@ "mympd" ], "dependencies": { - "bootstrap": "^5.3.2" + "bootstrap": "^5.3.3" }, "devDependencies": { "autoprefixer": "^10.2.5", diff --git a/dist/bootstrap/scss/custom.scss b/dist/bootstrap/scss/custom.scss index a3ba06801..509fbd39b 100644 --- a/dist/bootstrap/scss/custom.scss +++ b/dist/bootstrap/scss/custom.scss @@ -62,7 +62,6 @@ $link-color: $body-color; $link-color-dark: $body-color-dark; //other adjustments -$offcanvas-padding-x: 0; $popover-max-width: none; $breadcrumb-bg: $gray-200; diff --git a/dist/libmympdclient/CMakeLists.txt b/dist/libmympdclient/CMakeLists.txt index 5e5eab46c..3d8591317 100644 --- a/dist/libmympdclient/CMakeLists.txt +++ b/dist/libmympdclient/CMakeLists.txt @@ -2,11 +2,11 @@ # myMPD (c) 2018-2024 Juergen Mang # https://github.com/jcorporation/mympd -add_library(libmympdclient "") +add_library(mympdclient "") -target_include_directories(libmympdclient PUBLIC include) +target_include_directories(mympdclient PUBLIC include) -target_sources(libmympdclient +target_sources(mympdclient PRIVATE src/albumart.c src/async.c diff --git a/dist/libmympdclient/include/mpd/client.h b/dist/libmympdclient/include/mpd/client.h index b3f2ff5bb..90e1cca7e 100644 --- a/dist/libmympdclient/include/mpd/client.h +++ b/dist/libmympdclient/include/mpd/client.h @@ -33,7 +33,6 @@ #include "entity.h" #include "fingerprint.h" #include "idle.h" -#include "libmympdclient_version.h" #include "list.h" #include "message.h" #include "mixer.h" diff --git a/dist/libmympdclient/include/mpd/libmympdclient_version.h b/dist/libmympdclient/include/mpd/libmympdclient_version.h deleted file mode 100644 index 87eb6f841..000000000 --- a/dist/libmympdclient/include/mpd/libmympdclient_version.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - SPDX-License-Identifier: GPL-2.0-or-later - myMPD (c) 2018-2019 Juergen Mang - https://github.com/jcorporation/libmpdclient -*/ - -/*! \file - * patched libmpdclient library for usage in myMPD - * - * Do not include this header directly. Use mpd/client.h instead. - */ - -#ifndef LIBMYMPDCLIENT_VERSION_H -#define LIBMYMPDCLIENT_VERSION_H - -#define LIBMYMPDCLIENT_MAJOR_VERSION 1 -#define LIBMYMPDCLIENT_MINOR_VERSION 0 -#define LIBMYMPDCLIENT_PATCH_VERSION 26 - -/** - * Preprocessor macro which allows you to check which version of - * libmpdclient you are compiling with. It can be used in - * preprocessor directives. - * - * @return true if this libmympdclient version equals or is newer than - * the specified version number - */ -#define LIBMYMPDCLIENT_CHECK_VERSION(major, minor, patch) \ - ((major) < LIBMYMPDCLIENT_MAJOR_VERSION || \ - ((major) == LIBMYMPDCLIENT_MAJOR_VERSION && \ - ((minor) < LIBMYMPDCLIENT_MINOR_VERSION || \ - ((minor) == LIBMYMPDCLIENT_MINOR_VERSION && \ - (patch) <= LIBMYMPDCLIENT_PATCH_VERSION)))) - -#endif diff --git a/dist/libmympdclient/include/mpd/playlist.h b/dist/libmympdclient/include/mpd/playlist.h index e1de44873..3e3fe4eda 100644 --- a/dist/libmympdclient/include/mpd/playlist.h +++ b/dist/libmympdclient/include/mpd/playlist.h @@ -140,6 +140,22 @@ mpd_recv_playlist(struct mpd_connection *connection); bool mpd_send_list_playlist(struct mpd_connection *connection, const char *name); +/** + * Like mpd_send_list_playlist(), but specifies a (position) range. + * Use mpd_recv_entity() to receive the songs (#MPD_ENTITY_TYPE_SONG). + * + * @param connection the connection to MPD + * @param name the name of the playlist + * @param start the start position of the range (including) + * @param end the end position of the range (excluding); the special + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_send_list_playlist_range(struct mpd_connection *connection, const char *name, + unsigned start, unsigned end); + /** * List the content, with full metadata, of the stored playlist identified by * name. Use mpd_recv_entity() to receive the songs (#MPD_ENTITY_TYPE_SONG). @@ -151,6 +167,22 @@ mpd_send_list_playlist(struct mpd_connection *connection, const char *name); bool mpd_send_list_playlist_meta(struct mpd_connection *connection, const char *name); +/** + * Like mpd_send_list_playlist_meta(), but specifies a (position) range. + * Use mpd_recv_entity() to receive the songs (#MPD_ENTITY_TYPE_SONG). + * + * @param connection the connection to MPD + * @param name the name of the playlist + * @param start the start position of the range (including) + * @param end the end position of the range (excluding); the special + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_send_list_playlist_range_meta(struct mpd_connection *connection, const char *name, + unsigned start, unsigned end); + /** * Clear the playlist name (i.e. truncate name.m3u) * @@ -554,6 +586,19 @@ mpd_send_rm(struct mpd_connection *connection, const char *name); bool mpd_run_rm(struct mpd_connection *connection, const char *name); +/** + * Count the number of songs and their total playtime (seconds) in the + * playlist. + * + * @param connection the connection to MPD + * @param name the name of the playlist file + * @return true on success, false on error + * + * @since libmpdclient 2.23, MPD 0.24 + */ +bool +mpd_send_playlistlength(struct mpd_connection *connection, const char *name); + #ifdef __cplusplus } #endif diff --git a/dist/libmympdclient/include/mpd/sticker.h b/dist/libmympdclient/include/mpd/sticker.h index c78a4b566..69e5e84ac 100644 --- a/dist/libmympdclient/include/mpd/sticker.h +++ b/dist/libmympdclient/include/mpd/sticker.h @@ -30,6 +30,8 @@ enum mpd_sticker_operator { MPD_STICKER_OP_EQ_INT, MPD_STICKER_OP_GT_INT, MPD_STICKER_OP_LT_INT, + MPD_STICKER_OP_CONTAINS, + MPD_STICKER_OP_STARTS_WITH, }; /** diff --git a/dist/libmympdclient/include/mpd/version.h b/dist/libmympdclient/include/mpd/version.h index 8d3e8356f..baba29126 100644 --- a/dist/libmympdclient/include/mpd/version.h +++ b/dist/libmympdclient/include/mpd/version.h @@ -30,4 +30,23 @@ ((minor) == LIBMPDCLIENT_MINOR_VERSION && \ (patch) <= LIBMPDCLIENT_PATCH_VERSION)))) +#define LIBMYMPDCLIENT_MAJOR_VERSION 1 +#define LIBMYMPDCLIENT_MINOR_VERSION 0 +#define LIBMYMPDCLIENT_PATCH_VERSION 28 + +/** + * Preprocessor macro which allows you to check which version of + * libmympdclient you are compiling with. It can be used in + * preprocessor directives. + * + * @return true if this libmympdclient version equals or is newer than + * the specified version number + */ +#define LIBMYMPDCLIENT_CHECK_VERSION(major, minor, patch) \ + ((major) < LIBMYMPDCLIENT_MAJOR_VERSION || \ + ((major) == LIBMYMPDCLIENT_MAJOR_VERSION && \ + ((minor) < LIBMYMPDCLIENT_MINOR_VERSION || \ + ((minor) == LIBMYMPDCLIENT_MINOR_VERSION && \ + (patch) <= LIBMYMPDCLIENT_PATCH_VERSION)))) + #endif diff --git a/dist/libmympdclient/include/mpd/version.h.in b/dist/libmympdclient/include/mpd/version.h.in index b9c6f9923..08abf8e53 100644 --- a/dist/libmympdclient/include/mpd/version.h.in +++ b/dist/libmympdclient/include/mpd/version.h.in @@ -30,4 +30,23 @@ ((minor) == LIBMPDCLIENT_MINOR_VERSION && \ (patch) <= LIBMPDCLIENT_PATCH_VERSION)))) +#define LIBMYMPDCLIENT_MAJOR_VERSION 1 +#define LIBMYMPDCLIENT_MINOR_VERSION 0 +#define LIBMYMPDCLIENT_PATCH_VERSION 28 + +/** + * Preprocessor macro which allows you to check which version of + * libmympdclient you are compiling with. It can be used in + * preprocessor directives. + * + * @return true if this libmympdclient version equals or is newer than + * the specified version number + */ +#define LIBMYMPDCLIENT_CHECK_VERSION(major, minor, patch) \ + ((major) < LIBMYMPDCLIENT_MAJOR_VERSION || \ + ((major) == LIBMYMPDCLIENT_MAJOR_VERSION && \ + ((minor) < LIBMYMPDCLIENT_MINOR_VERSION || \ + ((minor) == LIBMYMPDCLIENT_MINOR_VERSION && \ + (patch) <= LIBMYMPDCLIENT_PATCH_VERSION)))) + #endif diff --git a/dist/libmympdclient/src/cplaylist.c b/dist/libmympdclient/src/cplaylist.c index 855d1b359..c7d889d82 100644 --- a/dist/libmympdclient/src/cplaylist.c +++ b/dist/libmympdclient/src/cplaylist.c @@ -29,12 +29,26 @@ mpd_send_list_playlist(struct mpd_connection *connection, const char *name) return mpd_send_command(connection, "listplaylist", name, NULL); } +bool +mpd_send_list_playlist_range(struct mpd_connection *connection, const char *name, + unsigned start, unsigned end) +{ + return mpd_send_s_range_command(connection, "listplaylist", name, start, end); +} + bool mpd_send_list_playlist_meta(struct mpd_connection *connection, const char *name) { return mpd_send_command(connection, "listplaylistinfo", name, NULL); } +bool +mpd_send_list_playlist_range_meta(struct mpd_connection *connection, const char *name, + unsigned start, unsigned end) +{ + return mpd_send_s_range_command(connection, "listplaylistinfo", name, start, end); +} + bool mpd_send_playlist_clear(struct mpd_connection *connection, const char *name) { @@ -299,3 +313,9 @@ mpd_run_rm(struct mpd_connection *connection, const char *name) mpd_send_rm(connection, name) && mpd_response_finish(connection); } + +bool +mpd_send_playlistlength(struct mpd_connection *connection, const char *name) +{ + return mpd_send_command(connection, "playlistlength", name, NULL); +} diff --git a/dist/libmympdclient/src/sticker.c b/dist/libmympdclient/src/sticker.c index 38245ab8c..496733dfd 100644 --- a/dist/libmympdclient/src/sticker.c +++ b/dist/libmympdclient/src/sticker.c @@ -192,13 +192,15 @@ mpd_sticker_search_begin(struct mpd_connection *connection, const char *type, static const char *get_sticker_oper_str(enum mpd_sticker_operator oper) { switch(oper) { - case MPD_STICKER_OP_EQ: return "="; - case MPD_STICKER_OP_GT: return ">"; - case MPD_STICKER_OP_LT: return "<"; - case MPD_STICKER_OP_EQ_INT: return "eq"; - case MPD_STICKER_OP_GT_INT: return "gt"; - case MPD_STICKER_OP_LT_INT: return "lt"; - case MPD_STICKER_OP_UNKOWN: return NULL; + case MPD_STICKER_OP_EQ: return "="; + case MPD_STICKER_OP_GT: return ">"; + case MPD_STICKER_OP_LT: return "<"; + case MPD_STICKER_OP_EQ_INT: return "eq"; + case MPD_STICKER_OP_GT_INT: return "gt"; + case MPD_STICKER_OP_LT_INT: return "lt"; + case MPD_STICKER_OP_CONTAINS: return "contains"; + case MPD_STICKER_OP_STARTS_WITH: return "starts_with"; + case MPD_STICKER_OP_UNKOWN: return NULL; } return NULL; } diff --git a/dist/mongoose/mongoose.c b/dist/mongoose/mongoose.c index e546ae06e..37bbac6ee 100644 --- a/dist/mongoose/mongoose.c +++ b/dist/mongoose/mongoose.c @@ -1268,9 +1268,9 @@ static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, pkt.header.flags = mg_htons(0x100); pkt.header.num_questions = mg_htons(1); for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { - if (name->ptr[i] == '.' || i >= name->len) { + if (name->buf[i] == '.' || i >= name->len) { pkt.data[n] = (uint8_t) (i - n); - memcpy(&pkt.data[n + 1], name->ptr + n, i - n); + memcpy(&pkt.data[n + 1], name->buf + n, i - n); n = i + 1; } if (i >= name->len) break; @@ -1308,7 +1308,7 @@ static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, d->c = c; c->is_resolving = 1; MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, - name->ptr, dnsc->url, d->txnid)); + name->buf, dnsc->url, d->txnid)); if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { mg_error(dnsc->c, "DNS send"); } @@ -1624,15 +1624,15 @@ struct mg_str mg_file_read(struct mg_fs *fs, const char *path) { void *fp; fs->st(path, &result.len, NULL); if ((fp = fs->op(path, MG_FS_READ)) != NULL) { - result.ptr = (char *) calloc(1, result.len + 1); - if (result.ptr != NULL && - fs->rd(fp, (void *) result.ptr, result.len) != result.len) { - free((void *) result.ptr); - result.ptr = NULL; + result.buf = (char *) calloc(1, result.len + 1); + if (result.buf != NULL && + fs->rd(fp, (void *) result.buf, result.len) != result.len) { + free((void *) result.buf); + result.buf = NULL; } fs->cl(fp); } - if (result.ptr == NULL) result.len = 0; + if (result.buf == NULL) result.len = 0; return result; } @@ -1674,10 +1674,10 @@ bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { // ... static void mg_fs_ls_fn(const char *filename, void *param) { struct mg_str *s = (struct mg_str *) param; - if (s->ptr[0] == '\0') { - mg_snprintf((char *) s->ptr, s->len, "%s", filename); - } else if (strcmp(s->ptr, filename) == 0) { - ((char *) s->ptr)[0] = '\0'; // Fetch next file + if (s->buf[0] == '\0') { + mg_snprintf((char *) s->buf, s->len, "%s", filename); + } else if (strcmp(s->buf, filename) == 0) { + ((char *) s->buf)[0] = '\0'; // Fetch next file } } @@ -2230,17 +2230,17 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, bool mg_to_size_t(struct mg_str str, size_t *val); bool mg_to_size_t(struct mg_str str, size_t *val) { size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0; - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; - if (i < str.len && str.ptr[i] == '-') return false; - while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') { - size_t digit = (size_t) (str.ptr[i] - '0'); + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; + if (i < str.len && str.buf[i] == '-') return false; + while (i < str.len && str.buf[i] >= '0' && str.buf[i] <= '9') { + size_t digit = (size_t) (str.buf[i] - '0'); if (result > max2) return false; // Overflow result *= 10; if (result > max - digit) return false; // Overflow result += digit; i++, ndigits++; } - while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; + while (i < str.len && (str.buf[i] == ' ' || str.buf[i] == '\t')) i++; if (ndigits == 0) return false; // #2322: Content-Length = 1 * DIGIT if (i != str.len) return false; // Ditto *val = (size_t) result; @@ -2265,7 +2265,7 @@ bool mg_to_size_t(struct mg_str str, size_t *val) { size_t mg_http_next_multipart(struct mg_str body, size_t ofs, struct mg_http_part *part) { struct mg_str cd = mg_str_n("Content-Disposition", 19); - const char *s = body.ptr; + const char *s = body.buf; size_t b = ofs, h1, h2, b1, b2, max = body.len; // Init part params @@ -2284,7 +2284,7 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs, if (h2 + 2 >= max) return 0; // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && - mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { + mg_ncasecmp(&s[h1], cd.buf, cd.len) == 0) { struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); @@ -2312,12 +2312,12 @@ void mg_http_bauth(struct mg_connection *c, const char *user, char *buf = (char *) &c->send.buf[c->send.len]; memcpy(buf, "Authorization: Basic ", 21); // DON'T use mg_send! for (i = 0; i < u.len; i++) { - n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n); + n = mg_base64_update(((unsigned char *) u.buf)[i], buf + 21, n); } if (p.len > 0) { n = mg_base64_update(':', buf + 21, n); for (i = 0; i < p.len; i++) { - n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n); + n = mg_base64_update(((unsigned char *) p.buf)[i], buf + 21, n); } } n = mg_base64_final(buf + 21, n); @@ -2332,7 +2332,7 @@ struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { struct mg_str entry, k, v, result = mg_str_n(NULL, 0); while (mg_span(buf, &entry, &buf, '&')) { if (mg_span(entry, &k, &v, '=') && name.len == k.len && - mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) { + mg_ncasecmp(name.buf, k.buf, k.len) == 0) { result = v; break; } @@ -2348,14 +2348,14 @@ int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, } if (dst == NULL || dst_len == 0) { len = -2; // Bad destination - } else if (buf->ptr == NULL || name == NULL || buf->len == 0) { + } else if (buf->buf == NULL || name == NULL || buf->len == 0) { len = -1; // Bad source } else { struct mg_str v = mg_http_var(*buf, mg_str(name)); - if (v.ptr == NULL) { + if (v.buf == NULL) { len = -4; // Name does not exist } else { - len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1); + len = mg_url_decode(v.buf, v.len, dst, dst_len, 1); if (len < 0) len = -3; // Failed to decode } } @@ -2407,7 +2407,7 @@ struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); for (i = 0; i < max && h->headers[i].name.len > 0; i++) { struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; - if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v; + if (n == k->len && mg_ncasecmp(k->buf, name, n) == 0) return v; } return NULL; } @@ -2431,7 +2431,7 @@ static size_t clen(const char *s, const char *end) { // Skip until the newline. Return advanced `s`, or NULL on error static const char *skiptorn(const char *s, const char *end, struct mg_str *v) { - v->ptr = s; + v->buf = (char *) s; while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r if (s < end && s[0] == '\r') s++; // Skip \r @@ -2446,7 +2446,7 @@ static bool mg_http_parse_headers(const char *s, const char *end, struct mg_str k = {NULL, 0}, v = {NULL, 0}; if (s >= end) return false; if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break; - k.ptr = s; + k.buf = (char *) s; while (s < end && s[0] != ':' && (n = clen(s, end)) > 0) s += n, k.len += n; if (k.len == 0) return false; // Empty name if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 @@ -2454,8 +2454,8 @@ static bool mg_http_parse_headers(const char *s, const char *end, // if (clen(s, end) == 0) return false; // Invalid UTF-8 while (s < end && s[0] == ' ') s++; // Skip spaces if ((s = skiptorn(s, end, &v)) == NULL) return false; - while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces - // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr)); + while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim spaces + // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); h[i].name = k, h[i].value = v; // Success. Assign values } return true; @@ -2464,31 +2464,31 @@ static bool mg_http_parse_headers(const char *s, const char *end, int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL - struct mg_str *cl; + const struct mg_str *cl; size_t n; memset(hm, 0, sizeof(*hm)); if (req_len <= 0) return req_len; - hm->message.ptr = hm->head.ptr = s; - hm->body.ptr = end; + hm->message.buf = hm->head.buf = (char *) s; + hm->body.buf = (char *) end; hm->head.len = (size_t) req_len; hm->message.len = hm->body.len = (size_t) -1; // Set body length to infinite // Parse request line - hm->method.ptr = s; + hm->method.buf = (char *) s; while (s < end && (n = clen(s, end)) > 0) s += n, hm->method.len += n; while (s < end && s[0] == ' ') s++; // Skip spaces - hm->uri.ptr = s; + hm->uri.buf = (char *) s; while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; while (s < end && s[0] == ' ') s++; // Skip spaces if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; // If URI contains '?' character, setup query string - if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) { - hm->query.ptr = qs + 1; - hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1)); - hm->uri.len = (size_t) (qs - hm->uri.ptr); + if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) { + hm->query.buf = (char *) qs + 1; + hm->query.len = (size_t) (&hm->uri.buf[hm->uri.len] - (qs + 1)); + hm->uri.len = (size_t) (qs - hm->uri.buf); } // Sanity check. Allow protocol/reason to be empty @@ -2515,7 +2515,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { // // So, if it is HTTP request, and Content-Length is not set, // and method is not (PUT or POST) then reset body length to zero. - is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0; + is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0; if (hm->body.len == (size_t) ~0 && !is_response && mg_vcasecmp(&hm->method, "PUT") != 0 && mg_vcasecmp(&hm->method, "POST") != 0) { @@ -2687,6 +2687,7 @@ static void static_cb(struct mg_connection *c, int ev, void *ev_data) { // Known mime types. Keep it outside guess_content_type() function, since // some environments don't like it defined there. // clang-format off +#define MG_C_STR(a) { (char *) (a), sizeof(a) - 1 } static struct mg_str s_known_types[] = { MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), @@ -2727,8 +2728,8 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) { size_t i = 0; // Shrink path to its extension only - while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; - path.ptr += path.len - i; + while (i < path.len && path.buf[path.len - i - 1] != '.') i++; + path.buf += path.len - i; path.len = i; // Process user-provided mime type overrides, if any @@ -2737,7 +2738,7 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) { } // Process built-in mime types - for (i = 0; s_known_types[i].ptr != NULL; i += 2) { + for (i = 0; s_known_types[i].buf != NULL; i += 2) { if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; } @@ -2747,8 +2748,8 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) { static int getrange(struct mg_str *s, size_t *a, size_t *b) { size_t i, numparsed = 0; for (i = 0; i + 6 < s->len; i++) { - struct mg_str k, v = mg_str_n(s->ptr + i + 6, s->len - i - 6); - if (memcmp(&s->ptr[i], "bytes=", 6) != 0) continue; + struct mg_str k, v = mg_str_n(s->buf + i + 6, s->len - i - 6); + if (memcmp(&s->buf[i], "bytes=", 6) != 0) continue; if (mg_span(v, &k, &v, '-')) { if (mg_to_size_t(k, a)) numparsed++; if (v.len > 0 && mg_to_size_t(v, b)) numparsed++; @@ -2831,7 +2832,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, "Etag: %s\r\n" "Content-Length: %llu\r\n" "%s%s%s\r\n", - status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, + status, mg_http_status_code_str(status), (int) mime.len, mime.buf, etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { @@ -2926,7 +2927,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, struct printdirentrydata d = {c, hm, opts, dir}; char tmp[10], buf[MG_PATH_MAX]; size_t off, n; - int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0); + int len = mg_url_decode(hm->uri.buf, hm->uri.len, buf, sizeof(buf), 0); struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; mg_printf(c, @@ -2945,8 +2946,8 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, "Size" "" "\n", - (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, - c->mgr->directory_listing_css, (int) uri.len, uri.ptr); + (int) uri.len, uri.buf, sort_js_code, sort_js_code2, + c->mgr->directory_listing_css, (int) uri.len, uri.buf); mg_printf(c, "%s", " .." "[DIR]\n"); @@ -2969,7 +2970,7 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, char *path, size_t path_size) { int flags, tmp; // Append URI to the root_dir, and sanitize it - size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.buf); if (n + 2 >= path_size) { mg_http_reply(c, 400, "", "Exceeded path size"); return -1; @@ -2978,29 +2979,29 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, // Terminate root dir with slash if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; if (url.len < hm->uri.len) { - mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n, + mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n, path_size - n, 0); } path[path_size - 1] = '\0'; // Double-check - if (!mg_path_is_sane(path)) { + if (!mg_path_is_sane(mg_str_n(path, path_size))) { mg_http_reply(c, 400, "", "Invalid path"); return -1; } n = strlen(path); while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL); - MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path, + MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.buf, path, flags)); if (flags == 0) { // Do nothing - let's caller decide } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && - hm->uri.ptr[hm->uri.len - 1] != '/') { + hm->uri.buf[hm->uri.len - 1] != '/') { mg_printf(c, "HTTP/1.1 301 Moved\r\n" "Location: %.*s/\r\n" "Content-Length: 0\r\n" "\r\n", - (int) hm->uri.len, hm->uri.ptr); + (int) hm->uri.len, hm->uri.buf); c->is_resp = 0; flags = -1; } else if (flags & MG_FS_DIR) { @@ -3032,7 +3033,7 @@ static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, if (!mg_span(part, &k, &v, '=')) k = part, v = mg_str_n(NULL, 0); if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v; if (hm->uri.len < k.len) continue; - if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue; + if (mg_strcmp(k, mg_str_n(hm->uri.buf, k.len)) != 0) continue; u = k, p = v; } return uri_to_path2(c, hm, fs, u, p, path, path_size); @@ -3086,51 +3087,47 @@ void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, char *pass, size_t passlen) { struct mg_str *v = mg_http_get_header(hm, "Authorization"); user[0] = pass[0] = '\0'; - if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) { + if (v != NULL && v->len > 6 && memcmp(v->buf, "Basic ", 6) == 0) { char buf[256]; - size_t n = mg_base64_decode(v->ptr + 6, v->len - 6, buf, sizeof(buf)); + size_t n = mg_base64_decode(v->buf + 6, v->len - 6, buf, sizeof(buf)); const char *p = (const char *) memchr(buf, ':', n > 0 ? n : 0); if (p != NULL) { mg_snprintf(user, userlen, "%.*s", p - buf, buf); mg_snprintf(pass, passlen, "%.*s", n - (size_t) (p - buf) - 1, p + 1); } - } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { - mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); + } else if (v != NULL && v->len > 7 && memcmp(v->buf, "Bearer ", 7) == 0) { + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->buf + 7); } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); - if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.buf); } else { mg_http_get_var(&hm->query, "access_token", pass, passlen); } } static struct mg_str stripquotes(struct mg_str s) { - return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' - ? mg_str_n(s.ptr + 1, s.len - 2) + return s.len > 1 && s.buf[0] == '"' && s.buf[s.len - 1] == '"' + ? mg_str_n(s.buf + 1, s.len - 2) : s; } struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { size_t i; for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { - if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { - const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; + if (s.buf[i + v.len] == '=' && memcmp(&s.buf[i], v.buf, v.len) == 0) { + const char *p = &s.buf[i + v.len + 1], *b = p, *x = &s.buf[s.len]; int q = p < x && *p == '"' ? 1 : 0; while (p < x && (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) p++; - // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, - // v.ptr, (int) (p - b), b)); + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.buf, (int) v.len, + // v.buf, (int) (p - b), b)); return stripquotes(mg_str_n(b, (size_t) (p - b + q))); } } return mg_str_n(NULL, 0); } -bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { - return mg_match(hm->uri, mg_str(glob), NULL); -} - long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, const char *dir, size_t max_size) { char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX]; @@ -3144,7 +3141,7 @@ long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, } else if (file[0] == '\0') { mg_http_reply(c, 400, "", "file required"); res = -1; - } else if (mg_path_is_sane(file) == false) { + } else if (mg_path_is_sane(mg_str(file)) == false) { mg_http_reply(c, 400, "", "%s: invalid file", file); res = -2; } else if (offset < 0) { @@ -3167,7 +3164,7 @@ long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, mg_http_reply(c, 400, "", "open(%s): %d", path, errno); res = -6; } else { - res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len); + res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len); mg_fs_close(fd); mg_http_reply(c, 200, "", "%ld", res); } @@ -3176,7 +3173,7 @@ long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, } int mg_http_status(const struct mg_http_message *hm) { - return atoi(hm->uri.ptr); + return atoi(hm->uri.buf); } static bool is_hex_digit(int c) { @@ -3221,7 +3218,7 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers if (ev == MG_EV_CLOSE) { // If client did not set Content-Length hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG - hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); + hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf); } if ((te = mg_http_get_header(&hm, "Transfer-Encoding")) != NULL) { if (mg_vcasecmp(te, "chunked") == 0) { @@ -3233,7 +3230,7 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { } else if (mg_http_get_header(&hm, "Content-length") == NULL) { // #2593: HTTP packets must contain either Transfer-Encoding or // Content-length - bool is_response = mg_ncasecmp(hm.method.ptr, "HTTP/", 5) == 0; + bool is_response = mg_ncasecmp(hm.method.buf, "HTTP/", 5) == 0; bool require_content_len = false; if (!is_response && (mg_vcasecmp(&hm.method, "POST") == 0 || mg_vcasecmp(&hm.method, "PUT") == 0)) { @@ -3292,11 +3289,11 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) { static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (mg_http_match_uri(hm, "/quit")) { + if (mg_match(hm->uri, mg_str("/quit"), NULL)) { mg_http_reply(c, 200, "", "ok\n"); c->is_draining = 1; c->data[0] = 'X'; - } else if (mg_http_match_uri(hm, "/debug")) { + } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) { int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); mg_log_set(level); mg_http_reply(c, 200, "", "Debug level set to %d\n", level); @@ -3486,52 +3483,52 @@ size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, struct mg_str *val) { if (ofs >= obj.len) { ofs = 0; // Out of boundaries, stop scanning - } else if (obj.len < 2 || (*obj.ptr != '{' && *obj.ptr != '[')) { + } else if (obj.len < 2 || (*obj.buf != '{' && *obj.buf != '[')) { ofs = 0; // Not an array or object, stop } else { - struct mg_str sub = mg_str_n(obj.ptr + ofs, obj.len - ofs); - if (ofs == 0) ofs++, sub.ptr++, sub.len--; - if (*obj.ptr == '[') { // Iterate over an array + struct mg_str sub = mg_str_n(obj.buf + ofs, obj.len - ofs); + if (ofs == 0) ofs++, sub.buf++, sub.len--; + if (*obj.buf == '[') { // Iterate over an array int n = 0, o = mg_json_get(sub, "$", &n); if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { ofs = 0; // Error parsing key, stop scanning } else { if (key) *key = mg_str_n(NULL, 0); - if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); - ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); } } else { // Iterate over an object int n = 0, o = mg_json_get(sub, "$", &n); if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { ofs = 0; // Error parsing key, stop scanning } else { - if (key) *key = mg_str_n(sub.ptr + o, (size_t) n); - sub.ptr += o + n, sub.len -= (size_t) (o + n); - while (sub.len > 0 && *sub.ptr != ':') sub.len--, sub.ptr++; - if (sub.len > 0 && *sub.ptr == ':') sub.len--, sub.ptr++; + if (key) *key = mg_str_n(sub.buf + o, (size_t) n); + sub.buf += o + n, sub.len -= (size_t) (o + n); + while (sub.len > 0 && *sub.buf != ':') sub.len--, sub.buf++; + if (sub.len > 0 && *sub.buf == ':') sub.len--, sub.buf++; n = 0, o = mg_json_get(sub, "$", &n); if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { ofs = 0; // Error parsing value, stop scanning } else { - if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); - ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + if (val) *val = mg_str_n(sub.buf + o, (size_t) n); + ofs = (size_t) (&sub.buf[o + n] - obj.buf); } } } - // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.ptr)); + // MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.buf)); while (ofs && ofs < obj.len && - (obj.ptr[ofs] == ' ' || obj.ptr[ofs] == '\t' || - obj.ptr[ofs] == '\n' || obj.ptr[ofs] == '\r')) { + (obj.buf[ofs] == ' ' || obj.buf[ofs] == '\t' || + obj.buf[ofs] == '\n' || obj.buf[ofs] == '\r')) { ofs++; } - if (ofs && ofs < obj.len && obj.ptr[ofs] == ',') ofs++; + if (ofs && ofs < obj.len && obj.buf[ofs] == ',') ofs++; if (ofs > obj.len) ofs = 0; } return ofs; } int mg_json_get(struct mg_str json, const char *path, int *toklen) { - const char *s = json.ptr; + const char *s = json.buf; int len = (int) json.len; enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; unsigned char nesting[MG_JSON_MAX_DEPTH]; @@ -3675,15 +3672,15 @@ int mg_json_get(struct mg_str json, const char *path, int *toklen) { struct mg_str mg_json_get_tok(struct mg_str json, const char *path) { int len = 0, ofs = mg_json_get(json, path, &len); - return mg_str_n(ofs < 0 ? NULL : json.ptr + ofs, + return mg_str_n(ofs < 0 ? NULL : json.buf + ofs, (size_t) (len < 0 ? 0 : len)); } bool mg_json_get_num(struct mg_str json, const char *path, double *v) { int n, toklen, found = 0; if ((n = mg_json_get(json, path, &toklen)) >= 0 && - (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) { - if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL); + (json.buf[n] == '-' || (json.buf[n] >= '0' && json.buf[n] <= '9'))) { + if (v != NULL) *v = mg_atod(json.buf + n, toklen, NULL); found = 1; } return found; @@ -3691,8 +3688,8 @@ bool mg_json_get_num(struct mg_str json, const char *path, double *v) { bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { int found = 0, off = mg_json_get(json, path, NULL); - if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) { - if (v != NULL) *v = json.ptr[off] == 't'; + if (off >= 0 && (json.buf[off] == 't' || json.buf[off] == 'f')) { + if (v != NULL) *v = json.buf[off] == 't'; found = 1; } return found; @@ -3701,21 +3698,21 @@ bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { bool mg_json_unescape(struct mg_str s, char *to, size_t n) { size_t i, j; for (i = 0, j = 0; i < s.len && j < n; i++, j++) { - if (s.ptr[i] == '\\' && i + 5 < s.len && s.ptr[i + 1] == 'u') { + if (s.buf[i] == '\\' && i + 5 < s.len && s.buf[i + 1] == 'u') { // \uXXXX escape. We could process a simple one-byte chars // \u00xx from the ASCII range. More complex chars would require // dragging in a UTF8 library, which is too much for us - if (s.ptr[i + 2] != '0' || s.ptr[i + 3] != '0') return false; // Give up - ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s.ptr + i + 4, 2); + if (s.buf[i + 2] != '0' || s.buf[i + 3] != '0') return false; // Give up + ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s.buf + i + 4, 2); i += 5; - } else if (s.ptr[i] == '\\' && i + 1 < s.len) { - char c = json_esc(s.ptr[i + 1], 0); + } else if (s.buf[i] == '\\' && i + 1 < s.len) { + char c = json_esc(s.buf[i + 1], 0); if (c == 0) return false; to[j] = c; i++; } else { - to[j] = s.ptr[i]; + to[j] = s.buf[i]; } } if (j >= n) return false; @@ -3726,9 +3723,9 @@ bool mg_json_unescape(struct mg_str s, char *to, size_t n) { char *mg_json_get_str(struct mg_str json, const char *path) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && len > 1 && json.ptr[off] == '"') { + if (off >= 0 && len > 1 && json.buf[off] == '"') { if ((result = (char *) calloc(1, (size_t) len)) != NULL && - !mg_json_unescape(mg_str_n(json.ptr + off + 1, (size_t) (len - 2)), + !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), result, (size_t) len)) { free(result); result = NULL; @@ -3740,9 +3737,9 @@ char *mg_json_get_str(struct mg_str json, const char *path) { char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.ptr[off] == '"' && len > 1 && + if (off >= 0 && json.buf[off] == '"' && len > 1 && (result = (char *) calloc(1, (size_t) len)) != NULL) { - size_t k = mg_base64_decode(json.ptr + off + 1, (size_t) (len - 2), result, + size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result, (size_t) len); if (slen != NULL) *slen = (int) k; } @@ -3752,9 +3749,9 @@ char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); - if (off >= 0 && json.ptr[off] == '"' && len > 1 && + if (off >= 0 && json.buf[off] == '"' && len > 1 && (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { - mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result); + mg_unhex(json.buf + off + 1, (size_t) (len - 2), (uint8_t *) result); result[len / 2 - 1] = '\0'; if (slen != NULL) *slen = len / 2 - 1; } @@ -4224,9 +4221,9 @@ static void mg_send_mqtt_properties(struct mg_connection *c, switch (mqtt_prop_type_by_id(props[i].id)) { case MQTT_PROP_TYPE_STRING_PAIR: mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); - mg_send(c, props[i].key.ptr, props[i].key.len); + mg_send(c, props[i].key.buf, props[i].key.len); mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); + mg_send(c, props[i].val.buf, props[i].val.len); break; case MQTT_PROP_TYPE_BYTE: mg_send(c, &props[i].iv, sizeof(uint8_t)); @@ -4239,11 +4236,11 @@ static void mg_send_mqtt_properties(struct mg_connection *c, break; case MQTT_PROP_TYPE_STRING: mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); + mg_send(c, props[i].val.buf, props[i].val.len); break; case MQTT_PROP_TYPE_BINARY_DATA: mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.ptr, props[i].val.len); + mg_send(c, props[i].val.buf, props[i].val.len); break; case MQTT_PROP_TYPE_VARIABLE_INT: len = encode_varint(buf_v, props[i].iv); @@ -4255,8 +4252,8 @@ static void mg_send_mqtt_properties(struct mg_connection *c, size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, size_t ofs) { - uint8_t *i = (uint8_t *) msg->dgram.ptr + msg->props_start + ofs; - uint8_t *end = (uint8_t *) msg->dgram.ptr + msg->dgram.len; + uint8_t *i = (uint8_t *) msg->dgram.buf + msg->props_start + ofs; + uint8_t *end = (uint8_t *) msg->dgram.buf + msg->dgram.len; size_t new_pos = ofs, len; prop->id = i[0]; @@ -4267,10 +4264,10 @@ size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, switch (mqtt_prop_type_by_id(prop->id)) { case MQTT_PROP_TYPE_STRING_PAIR: prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->key.ptr = (char *) i + 2; + prop->key.buf = (char *) i + 2; i += 2 + prop->key.len; prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; + prop->val.buf = (char *) i + 2; new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len; break; case MQTT_PROP_TYPE_BYTE: @@ -4288,12 +4285,12 @@ size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, break; case MQTT_PROP_TYPE_STRING: prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; + prop->val.buf = (char *) i + 2; new_pos += 2 + prop->val.len; break; case MQTT_PROP_TYPE_BINARY_DATA: prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]); - prop->val.ptr = (char *) i + 2; + prop->val.buf = (char *) i + 2; new_pos += 2 + prop->val.len; break; case MQTT_PROP_TYPE_VARIABLE_INT: @@ -4331,7 +4328,7 @@ void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { total_len += 2 + (uint32_t) opts->pass.len; hdr[7] |= MQTT_HAS_PASSWORD; } - if (opts->topic.len > 0 && opts->message.len > 0) { + if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; hdr[7] |= MQTT_HAS_WILL; } @@ -4352,47 +4349,53 @@ void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); mg_send_u16(c, mg_htons((uint16_t) cid.len)); - mg_send(c, cid.ptr, cid.len); + mg_send(c, cid.buf, cid.len); if (hdr[7] & MQTT_HAS_WILL) { if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); + mg_send(c, opts->topic.buf, opts->topic.len); mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); - mg_send(c, opts->message.ptr, opts->message.len); + mg_send(c, opts->message.buf, opts->message.len); } if (opts->user.len > 0) { mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); - mg_send(c, opts->user.ptr, opts->user.len); + mg_send(c, opts->user.buf, opts->user.len); } if (opts->pass.len > 0) { mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); - mg_send(c, opts->pass.ptr, opts->pass.len); + mg_send(c, opts->pass.buf, opts->pass.len); } } -void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + uint16_t id = opts->retransmit_id; uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); size_t len = 2 + opts->topic.len + opts->message.len; MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, - (char *) opts->topic.ptr, (int) opts->message.len, - (char *) opts->message.ptr)); + (char *) opts->topic.buf, (int) opts->message.len, + (char *) opts->message.buf)); if (opts->qos > 0) len += 2; if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); + if (opts->qos > 0 && id != 0) flags |= 1 << 3; mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); - if (opts->qos > 0) { - if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); + mg_send(c, opts->topic.buf, opts->topic.len); + if (opts->qos > 0) { // need to send 'id' field + if (id == 0) { // generate new one if not resending + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + id = c->mgr->mqtt_id; + } + mg_send_u16(c, mg_htons(id)); } if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); - if (opts->message.len > 0) mg_send(c, opts->message.ptr, opts->message.len); + if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len); + return id; } void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { @@ -4406,7 +4409,7 @@ void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.ptr, opts->topic.len); + mg_send(c, opts->topic.buf, opts->topic.len); mg_send(c, &qos_, sizeof(qos_)); } @@ -4416,7 +4419,7 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, uint32_t n = 0, len_len = 0; memset(m, 0, sizeof(*m)); - m->dgram.ptr = (char *) buf; + m->dgram.buf = (char *) buf; if (len < 2) return MQTT_INCOMPLETE; m->cmd = (uint8_t) (buf[0] >> 4); m->qos = (buf[0] >> 1) & 3; @@ -4454,7 +4457,7 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, case MQTT_CMD_PUBLISH: { if (p + 2 > end) return MQTT_MALFORMED; m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); - m->topic.ptr = (char *) p + 2; + m->topic.buf = (char *) p + 2; p += 2 + m->topic.len; if (p > end) return MQTT_MALFORMED; if (m->qos > 0) { @@ -4471,7 +4474,7 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, p += len_len + m->props_size; } if (p > end) return MQTT_MALFORMED; - m->data.ptr = (char *) p; + m->data.buf = (char *) p; m->data.len = (size_t) (end - p); break; } @@ -4493,7 +4496,7 @@ static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { break; } else if (rc == MQTT_OK) { MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, mm.cmd, - (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr)); + (int) mm.dgram.len, (int) mm.data.len, mm.data.buf)); switch (mm.cmd) { case MQTT_CMD_CONNACK: mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); @@ -4506,7 +4509,7 @@ static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { break; case MQTT_CMD_PUBLISH: { /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, - mm.topic.ptr, (int) mm.data.len, mm.data.ptr));*/ + mm.topic.buf, (int) mm.data.len, mm.data.buf));*/ if (mm.qos > 0) { uint16_t id = mg_ntohs(mm.id); uint32_t remaining_len = sizeof(id); @@ -4638,18 +4641,18 @@ static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { uint8_t data[4] = {0, 0, 0, 0}; size_t i, num_dots = 0; for (i = 0; i < str.len; i++) { - if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { - int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); + if (str.buf[i] >= '0' && str.buf[i] <= '9') { + int octet = data[num_dots] * 10 + (str.buf[i] - '0'); if (octet > 255) return false; data[num_dots] = (uint8_t) octet; - } else if (str.ptr[i] == '.') { - if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; + } else if (str.buf[i] == '.') { + if (num_dots >= 3 || i == 0 || str.buf[i - 1] == '.') return false; num_dots++; } else { return false; } } - if (num_dots != 3 || str.ptr[i - 1] == '.') return false; + if (num_dots != 3 || str.buf[i - 1] == '.') return false; memcpy(&addr->ip, data, sizeof(data)); addr->is_ip6 = false; return true; @@ -4659,12 +4662,12 @@ static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { int i; uint32_t ipv4; if (str.len < 14) return false; - if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false; + if (str.buf[0] != ':' || str.buf[1] != ':' || str.buf[6] != ':') return false; for (i = 2; i < 6; i++) { - if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false; + if (str.buf[i] != 'f' && str.buf[i] != 'F') return false; } - // struct mg_str s = mg_str_n(&str.ptr[7], str.len - 7); - if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false; + // struct mg_str s = mg_str_n(&str.buf[7], str.len - 7); + if (!mg_aton4(mg_str_n(&str.buf[7], str.len - 7), addr)) return false; memcpy(&ipv4, addr->ip, sizeof(ipv4)); memset(addr->ip, 0, sizeof(addr->ip)); addr->ip[10] = addr->ip[11] = 255; @@ -4676,33 +4679,33 @@ static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { size_t i, j = 0, n = 0, dc = 42; addr->scope_id = 0; - if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2; + if (str.len > 2 && str.buf[0] == '[') str.buf++, str.len -= 2; if (mg_v4mapped(str, addr)) return true; for (i = 0; i < str.len; i++) { - if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || - (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || - (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { + if ((str.buf[i] >= '0' && str.buf[i] <= '9') || + (str.buf[i] >= 'a' && str.buf[i] <= 'f') || + (str.buf[i] >= 'A' && str.buf[i] <= 'F')) { unsigned long val; if (i > j + 3) return false; - // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j])); - val = mg_unhexn(&str.ptr[j], i - j + 1); + // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j])); + val = mg_unhexn(&str.buf[j], i - j + 1); addr->ip[n] = (uint8_t) ((val >> 8) & 255); addr->ip[n + 1] = (uint8_t) (val & 255); - } else if (str.ptr[i] == ':') { + } else if (str.buf[i] == ':') { j = i + 1; - if (i > 0 && str.ptr[i - 1] == ':') { + if (i > 0 && str.buf[i - 1] == ':') { dc = n; // Double colon - if (i > 1 && str.ptr[i - 2] == ':') return false; + if (i > 1 && str.buf[i - 2] == ':') return false; } else if (i > 0) { n += 2; } if (n > 14) return false; addr->ip[n] = addr->ip[n + 1] = 0; // For trailing :: - } else if (str.ptr[i] == '%') { // Scope ID + } else if (str.buf[i] == '%') { // Scope ID for (i = i + 1; i < str.len; i++) { - if (str.ptr[i] < '0' || str.ptr[i] > '9') return false; + if (str.buf[i] < '0' || str.buf[i] > '9') return false; addr->scope_id = (uint8_t) (addr->scope_id * 10); - addr->scope_id = (uint8_t) (addr->scope_id + (str.ptr[i] - '0')); + addr->scope_id = (uint8_t) (addr->scope_id + (str.buf[i] - '0')); } } else { return false; @@ -4719,7 +4722,7 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { } bool mg_aton(struct mg_str str, struct mg_addr *addr) { - // MG_INFO(("[%.*s]", (int) str.len, str.ptr)); + // MG_INFO(("[%.*s]", (int) str.len, str.buf)); return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); } @@ -4867,6 +4870,8 @@ void mg_mgr_init(struct mg_mgr *mgr) { // Ignore SIGPIPE signal, so if client cancels the request, it // won't kill the whole process. signal(SIGPIPE, SIG_IGN); +#elif MG_ENABLE_TCPIP_DRIVER_INIT && defined(MG_TCPIP_DRIVER_INIT) + MG_TCPIP_DRIVER_INIT(mgr); #endif mgr->pipe = MG_INVALID_SOCKET; mgr->dnstimeout = 3000; @@ -5022,7 +5027,7 @@ static void send_syn(struct mg_connection *c); static void mkpay(struct pkt *pkt, void *p) { pkt->pay = - mg_str_n((char *) p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) p)); + mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p)); } static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { @@ -5056,13 +5061,13 @@ static void settmout(struct mg_connection *c, uint8_t type) { } static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) { - size_t n = ifp->driver->tx(ifp->tx.ptr, len, ifp); + size_t n = ifp->driver->tx(ifp->tx.buf, len, ifp); if (n == len) ifp->nsent++; return n; } static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) { - struct eth *eth = (struct eth *) ifp->tx.ptr; + struct eth *eth = (struct eth *) ifp->tx.buf; struct arp *arp = (struct arp *) (eth + 1); memset(eth->dst, 255, sizeof(eth->dst)); memcpy(eth->src, ifp->mac, sizeof(eth->src)); @@ -5092,7 +5097,7 @@ static void onstatechange(struct mg_tcpip_if *ifp) { static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint8_t proto, uint32_t ip_src, uint32_t ip_dst, size_t plen) { - struct eth *eth = (struct eth *) ifp->tx.ptr; + struct eth *eth = (struct eth *) ifp->tx.buf; struct ip *ip = (struct ip *) (eth + 1); memcpy(eth->dst, mac_dst, sizeof(eth->dst)); memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC @@ -5205,7 +5210,7 @@ static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { // ARP request. Make a response, then send // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); - struct eth *eth = (struct eth *) ifp->tx.ptr; + struct eth *eth = (struct eth *) ifp->tx.buf; struct arp *arp = (struct arp *) (eth + 1); memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); memcpy(eth->src, ifp->mac, sizeof(eth->src)); @@ -5249,7 +5254,7 @@ static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { sizeof(struct icmp) + plen); struct icmp *icmp = (struct icmp *) (ip + 1); memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 - memcpy(icmp + 1, pkt->pay.ptr, plen); // Copy RX payload to TX + memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); ether_output(ifp, hlen + plen); } @@ -5260,7 +5265,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { uint8_t msgtype = 0, state = ifp->state; // perform size check first, then access fields uint8_t *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len]; + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; if (end < (uint8_t *) (pkt->dhcp + 1)) return; if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 @@ -5308,7 +5313,7 @@ static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { // Simple DHCP server that assigns a next IP address: ifp->ip + 1 static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { uint8_t op = 0, *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len]; + *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; if (end < (uint8_t *) (pkt->dhcp + 1)) return; // struct dhcp *req = pkt->dhcp; struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; @@ -5360,7 +5365,7 @@ static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { mg_error(c, "oom"); } else { - memcpy(&c->recv.buf[c->recv.len], pkt->pay.ptr, pkt->pay.len); + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); c->recv.len += pkt->pay.len; mg_call(c, MG_EV_READ, &pkt->pay.len); } @@ -5403,8 +5408,8 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, mg_ntohs(tcp->dport), tcp->flags, len)); - // mg_hexdump(ifp->tx.ptr, PDIFF(ifp->tx.ptr, tcp + 1) + len); - return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len); + // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len); + return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len); } static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, @@ -5493,6 +5498,17 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { return (long) len; } +static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) { + long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); + if (n == MG_IO_ERR) { + mg_error(c, "TLS recv error"); + } else if (n > 0) { + // Decrypted successfully - trigger MG_EV_READ + io->len += (size_t) n; + mg_call(c, MG_EV_READ, &n); + } +} + static void read_conn(struct mg_connection *c, struct pkt *pkt) { struct connstate *s = (struct connstate *) (c + 1); struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; @@ -5539,7 +5555,7 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { // therefore we copy that encrypted data to the c->rtls iobuffer instead, // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will // call back mg_io_recv() which grabs raw data from c->rtls - memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len); + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); io->len += pkt->pay.len; MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); @@ -5571,14 +5587,7 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { mg_error(c, "oom"); } else { // Decrypt data directly into c->recv - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); - if (n == MG_IO_ERR) { - mg_error(c, "TLS recv error"); - } else if (n > 0) { - // Decrypted successfully - trigger MG_EV_READ - io->len += (size_t) n; - mg_call(c, MG_EV_READ, &n); - } + handle_tls_recv(c, io); } } else { // Plain text connection, data is already in c->recv, trigger @@ -5601,7 +5610,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { settmout(c, MIP_TTYPE_KEEPALIVE); mg_call(c, MG_EV_CONNECT, NULL); // Let user know } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { - // mg_hexdump(pkt->raw.ptr, pkt->raw.len); + // mg_hexdump(pkt->raw.buf, pkt->raw.len); tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); } else if (c != NULL && pkt->tcp->flags & TH_RST) { mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 @@ -5610,7 +5619,7 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); - mg_hexdump(pkt->pay.ptr, pkt->pay.len); + mg_hexdump(pkt->pay.buf, pkt->pay.len); #endif s->tmiss = 0; // Reset missed keep-alive counter if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer @@ -5699,7 +5708,7 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { struct pkt pkt; memset(&pkt, 0, sizeof(pkt)); - pkt.raw.ptr = (char *) buf; + pkt.raw.buf = (char *) buf; pkt.raw.len = len; pkt.eth = (struct eth *) buf; // mg_hexdump(buf, len > 16 ? 16: len); @@ -5745,6 +5754,14 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now); ifp->now = now; +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + if (expired_1000ms) { + const char *names[] = {"down", "up", "req", "ready"}; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", + names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, + ifp->ndrop, ifp->nerr)); + } +#endif // Handle physical interface up/down status if (expired_1000ms && ifp->driver->up) { bool up = ifp->driver->up(ifp); @@ -5853,7 +5870,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { MG_ERROR(("driver init failed")); } else { size_t framesize = 1540; - ifp->tx.ptr = (char *) calloc(1, framesize), ifp->tx.len = framesize; + ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize; if (ifp->recv_queue.size == 0) ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); @@ -5867,13 +5884,13 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from // MG_EPHEMERAL_PORT_BASE to 65535 - if (ifp->tx.ptr == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); + if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); } } void mg_tcpip_free(struct mg_tcpip_if *ifp) { free(ifp->recv_queue.buf); - free((char *) ifp->tx.ptr); + free(ifp->tx.buf); } static void send_syn(struct mg_connection *c) { @@ -5900,7 +5917,7 @@ void mg_connect_resolved(struct mg_connection *c) { if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { struct connstate *s = (struct connstate *) (c + 1); memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast - } else if (((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) { + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) { // If we're in the same LAN, fire an ARP lookup. MG_DEBUG(("%lu ARP lookup...", c->id)); arp_ask(ifp, rem_ip); @@ -5971,8 +5988,9 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv; struct mg_connection *c, *tmp; uint64_t now = mg_millis(); - if (ifp != NULL && ifp->driver != NULL) mg_tcpip_poll(ifp, now); mg_timer_poll(&mgr->timers, now); + if (ifp == NULL || ifp->driver == NULL) return; + mg_tcpip_poll(ifp, now); for (c = mgr->conns; c != NULL; c = tmp) { tmp = c->next; struct connstate *s = (struct connstate *) (c + 1); @@ -5980,6 +5998,8 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); + if (c->is_tls && mg_tls_pending(c) > 0) + handle_tls_recv(c, (struct mg_iobuf *) &c->rtls); if (can_write(c)) write_conn(c); if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) init_closure(c); @@ -6550,7 +6570,7 @@ void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) { while ((r = *head) != NULL) { if (r->fn == fn || fn == NULL) { *head = r->next; - free((void *) r->method.ptr); + free((void *) r->method.buf); free(r); } else { head = &(*head)->next; @@ -6565,21 +6585,21 @@ static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) { r->rpc = h; h->fn(r); } else { - mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr); + mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.buf); } } void mg_rpc_process(struct mg_rpc_req *r) { int len, off = mg_json_get(r->frame, "$.method", &len); - if (off > 0 && r->frame.ptr[off] == '"') { - struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2); + if (off > 0 && r->frame.buf[off] == '"') { + struct mg_str method = mg_str_n(&r->frame.buf[off + 1], (size_t) len - 2); mg_rpc_call(r, method); } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 || (off = mg_json_get(r->frame, "$.error", &len)) > 0) { mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler } else { mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len, - r->frame.ptr); // Invalid + r->frame.buf); // Invalid } } @@ -6587,7 +6607,7 @@ void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) { int len, off = mg_json_get(r->frame, "$.id", &len); if (off > 0) { mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len, - &r->frame.ptr[off], mg_print_esc, 0, "result"); + &r->frame.buf[off], mg_print_esc, 0, "result"); mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); mg_xprintf(r->pfn, r->pfn_data, "}"); } @@ -6605,7 +6625,7 @@ void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) { mg_xprintf(r->pfn, r->pfn_data, "{"); if (off > 0) { mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len, - &r->frame.ptr[off]); + &r->frame.buf[off]); } mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error", mg_print_esc, 0, "code", code, mg_print_esc, 0, "message"); @@ -6626,7 +6646,7 @@ static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) { for (h = *head; h != NULL; h = h->next) { if (h->method.len == 0) continue; // Ignore response handler len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",", - mg_print_esc, (int) h->method.len, h->method.ptr); + mg_print_esc, (int) h->method.len, h->method.buf); } return len; } @@ -6850,6 +6870,12 @@ void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { #ifdef MG_ENABLE_LINES #line 1 "src/sha256.c" #endif +// https://github.com/B-Con/crypto-algorithms +// Author: Brad Conte (brad AT bradconte.com) +// Disclaimer: This code is presented "as is" without any guarantees. +// Details: Defines the API for the corresponding SHA1 implementation. +// Copyright: public domain + #define ror(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) @@ -6891,8 +6917,10 @@ static void mg_sha256_chunk(mg_sha256_ctx *ctx) { uint32_t a, b, c, d, e, f, g, h; uint32_t m[64]; for (i = 0, j = 0; i < 16; ++i, j += 4) - m[i] = (uint32_t) ((ctx->buffer[j] << 24) | (ctx->buffer[j + 1] << 16) | - (ctx->buffer[j + 2] << 8) | (ctx->buffer[j + 3])); + m[i] = (uint32_t) (((uint32_t) ctx->buffer[j] << 24) | + ((uint32_t) ctx->buffer[j + 1] << 16) | + ((uint32_t) ctx->buffer[j + 2] << 8) | + ((uint32_t) ctx->buffer[j + 3])); for (; i < 64; ++i) m[i] = sig1(m[i - 2]) + m[i - 7] + sig0(m[i - 15]) + m[i - 16]; @@ -6990,7 +7018,7 @@ void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, memset(i_pad, 0x36, sizeof(i_pad)); memset(o_pad, 0x5c, sizeof(o_pad)); if (keysz < 64) { - memmove(k, key, keysz); + if (keysz > 0) memmove(k, key, keysz); } else { mg_sha256_init(&ctx); mg_sha256_update(&ctx, key, keysz); @@ -7010,7 +7038,6 @@ void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, mg_sha256_final(dst, &ctx); } - #ifdef MG_ENABLE_LINES #line 1 "src/sntp.c" #endif @@ -7579,7 +7606,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { size_t max = 1; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_writable = 0; - if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1; if (can_write(c)) MG_EPOLL_MOD(c, 1); if (c->is_closing) ms = 1; max++; @@ -7595,7 +7622,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { bool wr = evs[i].events & EPOLLOUT; c->is_readable = can_read(c) && rd ? 1U : 0; c->is_writable = can_write(c) && wr ? 1U : 0; - if (mg_tls_pending(c) > 0) c->is_readable = 1; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; } } (void) skip_iotest; @@ -7609,7 +7636,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { c->is_readable = c->is_writable = 0; if (skip_iotest(c)) { // Socket not valid, ignore - } else if (mg_tls_pending(c) > 0) { + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) { ms = 1; // Don't wait if TLS is ready } else { fds[n].fd = FD(c); @@ -7631,7 +7658,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { if (skip_iotest(c)) { // Socket not valid, ignore - } else if (mg_tls_pending(c) > 0) { + } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) { c->is_readable = 1; } else { if (fds[n].revents & POLLERR) { @@ -7640,7 +7667,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { c->is_readable = (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0); c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0); - if (mg_tls_pending(c) > 0) c->is_readable = 1; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; } n++; } @@ -7662,7 +7689,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { FD_SET(FD(c), &eset); if (can_read(c)) FD_SET(FD(c), &rset); if (can_write(c)) FD_SET(FD(c), &wset); - if (mg_tls_pending(c) > 0) tvp = &tv_zero; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero; if (FD(c) > maxfd) maxfd = FD(c); if (c->is_closing) ms = 1; } @@ -7684,7 +7711,7 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) { } else { c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset); c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset); - if (mg_tls_pending(c) > 0) c->is_readable = 1; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) c->is_readable = 1; } } #endif @@ -7924,12 +7951,12 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root, struct mg_str mg_str_s(const char *s) { - struct mg_str str = {s, s == NULL ? 0 : strlen(s)}; + struct mg_str str = {(char *) s, s == NULL ? 0 : strlen(s)}; return str; } struct mg_str mg_str_n(const char *s, size_t n) { - struct mg_str str = {s, n}; + struct mg_str str = {(char *) s, n}; return str; } @@ -7953,26 +7980,26 @@ int mg_casecmp(const char *s1, const char *s2) { int mg_vcmp(const struct mg_str *s1, const char *s2) { size_t n2 = strlen(s2), n1 = s1->len; - int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2); + int r = strncmp(s1->buf, s2, (n1 < n2) ? n1 : n2); if (r == 0) return (int) (n1 - n2); return r; } int mg_vcasecmp(const struct mg_str *str1, const char *str2) { size_t n2 = strlen(str2), n1 = str1->len; - int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2); + int r = mg_ncasecmp(str1->buf, str2, (n1 < n2) ? n1 : n2); if (r == 0) return (int) (n1 - n2); return r; } struct mg_str mg_strdup(const struct mg_str s) { struct mg_str r = {NULL, 0}; - if (s.len > 0 && s.ptr != NULL) { + if (s.len > 0 && s.buf != NULL) { char *sc = (char *) calloc(1, s.len + 1); if (sc != NULL) { - memcpy(sc, s.ptr, s.len); + memcpy(sc, s.buf, s.len); sc[s.len] = '\0'; - r.ptr = sc; + r.buf = sc; r.len = s.len; } } @@ -7982,8 +8009,8 @@ struct mg_str mg_strdup(const struct mg_str s) { int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { size_t i = 0; while (i < str1.len && i < str2.len) { - int c1 = str1.ptr[i]; - int c2 = str2.ptr[i]; + int c1 = str1.buf[i]; + int c2 = str2.buf[i]; if (c1 < c2) return -1; if (c1 > c2) return 1; i++; @@ -7997,10 +8024,10 @@ const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle) { size_t i; if (needle.len > haystack.len) return NULL; - if (needle.len == 0) return haystack.ptr; + if (needle.len == 0) return haystack.buf; for (i = 0; i <= haystack.len - needle.len; i++) { - if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) { - return haystack.ptr + i; + if (memcmp(haystack.buf + i, needle.buf, needle.len) == 0) { + return haystack.buf + i; } } return NULL; @@ -8011,39 +8038,39 @@ static bool is_space(int c) { } struct mg_str mg_strstrip(struct mg_str s) { - while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--; - while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--; + while (s.len > 0 && is_space((int) *s.buf)) s.buf++, s.len--; + while (s.len > 0 && is_space((int) *(s.buf + s.len - 1))) s.len--; return s; } bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) { size_t i = 0, j = 0, ni = 0, nj = 0; - if (caps) caps->ptr = NULL, caps->len = 0; + if (caps) caps->buf = NULL, caps->len = 0; while (i < p.len || j < s.len) { - if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) { + if (i < p.len && j < s.len && (p.buf[i] == '?' || s.buf[j] == p.buf[i])) { if (caps == NULL) { - } else if (p.ptr[i] == '?') { - caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap - caps++, caps->ptr = NULL, caps->len = 0; // Init next cap - } else if (caps->ptr != NULL && caps->len == 0) { - caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize current cap - caps++, caps->len = 0, caps->ptr = NULL; // Init next cap + } else if (p.buf[i] == '?') { + caps->buf = &s.buf[j], caps->len = 1; // Finalize `?` cap + caps++, caps->buf = NULL, caps->len = 0; // Init next cap + } else if (caps->buf != NULL && caps->len == 0) { + caps->len = (size_t) (&s.buf[j] - caps->buf); // Finalize current cap + caps++, caps->len = 0, caps->buf = NULL; // Init next cap } i++, j++; - } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) { - if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j]; // Init cap + } else if (i < p.len && (p.buf[i] == '*' || p.buf[i] == '#')) { + if (caps && !caps->buf) caps->len = 0, caps->buf = &s.buf[j]; // Init cap ni = i++, nj = j + 1; - } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) { + } else if (nj > 0 && nj <= s.len && (p.buf[ni] == '#' || s.buf[j] != '/')) { i = ni, j = nj; - if (caps && caps->ptr == NULL && caps->len == 0) { + if (caps && caps->buf == NULL && caps->len == 0) { caps--, caps->len = 0; // Restart previous cap } } else { return false; } } - if (caps && caps->ptr && caps->len == 0) { - caps->len = (size_t) (&s.ptr[j] - caps->ptr); + if (caps && caps->buf && caps->len == 0) { + caps->len = (size_t) (&s.buf[j] - caps->buf); } return true; } @@ -8053,14 +8080,14 @@ bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { } bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char sep) { - if (s.len == 0 || s.ptr == NULL) { + if (s.len == 0 || s.buf == NULL) { return false; // Empty string, nothing to span - fail } else { size_t len = 0; - while (len < s.len && s.ptr[len] != sep) len++; // Find separator - if (a) *a = mg_str_n(s.ptr, len); // Init a - if (b) *b = mg_str_n(s.ptr + len, s.len - len); // Init b - if (b && len < s.len) b->ptr++, b->len--; // Skip separator + while (len < s.len && s.buf[len] != sep) len++; // Find separator + if (a) *a = mg_str_n(s.buf, len); // Init a + if (b) *b = mg_str_n(s.buf + len, s.len - len); // Init b + if (b && len < s.len) b->buf++, b->len--; // Skip separator return true; } } @@ -8096,11 +8123,12 @@ void mg_unhex(const char *buf, size_t len, unsigned char *to) { } } -bool mg_path_is_sane(const char *path) { - const char *s = path; - if (path[0] == '.' && path[1] == '.') return false; // Starts with .. - for (; s[0] != '\0'; s++) { - if (s[0] == '/' || s[0] == '\\') { // Subdir? +bool mg_path_is_sane(const struct mg_str path) { + const char *s = path.buf; + size_t n = path.len; + if (path.buf[0] == '.' && path.buf[1] == '.') return false; // Starts with .. + for (; s[0] != '\0' && n > 0; s++, n--) { + if ((s[0] == '/' || s[0] == '\\') && n >= 2) { // Subdir? if (s[1] == '.' && s[2] == '.') return false; // Starts with .. } } @@ -8176,10 +8204,177 @@ void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) { * *******************************************************************************/ +/******************************************************************************/ +#define AES_DECRYPTION 1 // whether AES decryption is supported +/******************************************************************************/ + +#define MG_ENCRYPT 1 // specify whether we're encrypting +#define MG_DECRYPT 0 // or decrypting + + #if MG_TLS == MG_TLS_BUILTIN +/****************************************************************************** + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use + ******************************************************************************/ +static void aes_init_keygen_tables(void); + +/****************************************************************************** + * AES_SETKEY : called to expand the key for encryption or decryption + ******************************************************************************/ +static int aes_setkey(aes_context *ctx, // pointer to context + int mode, // 1 or 0 for Encrypt/Decrypt + const uchar *key, // AES input key + uint keysize); // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) + // returns 0 for success + +/****************************************************************************** + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data + ******************************************************************************/ +static int aes_cipher(aes_context *ctx, // pointer to context + const uchar input[16], // 128-bit block to en/decipher + uchar output[16]); // 128-bit output result block + // returns 0 for success + +/****************************************************************************** + * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx + ******************************************************************************/ +typedef struct { + int mode; // cipher direction: encrypt/decrypt + uint64_t len; // cipher data length processed so far + uint64_t add_len; // total add data length + uint64_t HL[16]; // precalculated lo-half HTable + uint64_t HH[16]; // precalculated hi-half HTable + uchar base_ectr[16]; // first counter-mode cipher output for tag + uchar y[16]; // the current cipher-input IV|Counter value + uchar buf[16]; // buf working value + aes_context aes_ctx; // cipher context used +} gcm_context; + +/****************************************************************************** + * GCM_SETKEY : sets the GCM (and AES) keying material for use + ******************************************************************************/ +static int gcm_setkey( + gcm_context *ctx, // caller-provided context ptr + const uchar *key, // pointer to cipher key + const uint keysize // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) +); // returns 0 for success + +/****************************************************************************** + * + * GCM_CRYPT_AND_TAG + * + * This either encrypts or decrypts the user-provided data and, either + * way, generates an authentication tag of the requested length. It must be + * called with a GCM context whose key has already been set with GCM_SETKEY. + * + * The user would typically call this explicitly to ENCRYPT a buffer of data + * and optional associated data, and produce its an authentication tag. + * + * To reverse the process the user would typically call the companion + * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided + * authentication tag. The GCM_AUTH_DECRYPT function calls this function + * to perform its decryption and tag generation, which it then compares. + * + ******************************************************************************/ +static int gcm_crypt_and_tag( + gcm_context *ctx, // gcm context with key already setup + int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0) + const uchar *iv, // pointer to the 12-byte initialization vector + size_t iv_len, // byte length if the IV. should always be 12 + const uchar *add, // pointer to the non-ciphered additional data + size_t add_len, // byte length of the additional AEAD data + const uchar *input, // pointer to the cipher data source + uchar *output, // pointer to the cipher data destination + size_t length, // byte length of the cipher data + uchar *tag, // pointer to the tag to be generated + size_t tag_len); // byte length of the tag to be generated + +/****************************************************************************** + * + * GCM_START + * + * Given a user-provided GCM context, this initializes it, sets the encryption + * mode, and preprocesses the initialization vector and additional AEAD data. + * + ******************************************************************************/ +static int gcm_start( + gcm_context *ctx, // pointer to user-provided GCM context + int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0) + const uchar *iv, // pointer to initialization vector + size_t iv_len, // IV length in bytes (should == 12) + const uchar *add, // pointer to additional AEAD data (NULL if none) + size_t add_len); // length of additional AEAD data (bytes) + +/****************************************************************************** + * + * GCM_UPDATE + * + * This is called once or more to process bulk plaintext or ciphertext data. + * We give this some number of bytes of input and it returns the same number + * of output bytes. If called multiple times (which is fine) all but the final + * invocation MUST be called with length mod 16 == 0. (Only the final call can + * have a partial block length of < 128 bits.) + * + ******************************************************************************/ +static int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context + size_t length, // length, in bytes, of data to process + const uchar *input, // pointer to source data + uchar *output); // pointer to destination data + +/****************************************************************************** + * + * GCM_FINISH + * + * This is called once after all calls to GCM_UPDATE to finalize the GCM. + * It performs the final GHASH to produce the resulting authentication TAG. + * + ******************************************************************************/ +static int gcm_finish( + gcm_context *ctx, // pointer to user-provided GCM context + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 + size_t tag_len); // length, in bytes, of the tag-receiving buf + +/****************************************************************************** + * + * GCM_ZERO_CTX + * + * The GCM context contains both the GCM context and the AES context. + * This includes keying and key-related material which is security- + * sensitive, so it MUST be zeroed after use. This function does that. + * + ******************************************************************************/ +static void gcm_zero_ctx(gcm_context *ctx); + +/****************************************************************************** + * + * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL + * + * This is a simple and straightforward implementation of the AES Rijndael + * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus + * of this work was correctness & accuracy. It is written in 'C' without any + * particular focus upon optimization or speed. It should be endian (memory + * byte order) neutral since the few places that care are handled explicitly. + * + * This implementation of Rijndael was created by Steven M. Gibson of GRC.com. + * + * It is intended for general purpose use, but was written in support of GRC's + * reference implementation of the SQRL (Secure Quick Reliable Login) client. + * + * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html + * + * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE + * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. + * + *******************************************************************************/ + + + + static int aes_tables_inited = 0; // run-once flag for performing key // expasion table generation (see below) /* @@ -8233,34 +8428,34 @@ static uint32_t RCON[10]; // AES round constants /* * AES forward and reverse encryption round processing macros */ -#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ - { \ - X0 = *RK++ ^ FT0[(Y0) &0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \ - FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \ - \ - X1 = *RK++ ^ FT0[(Y1) &0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \ - FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \ - \ - X2 = *RK++ ^ FT0[(Y2) &0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \ - FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \ - \ - X3 = *RK++ ^ FT0[(Y3) &0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \ - FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \ - } - -#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ - { \ - X0 = *RK++ ^ RT0[(Y0) &0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \ - RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \ - \ - X1 = *RK++ ^ RT0[(Y1) &0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \ - RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \ - \ - X2 = *RK++ ^ RT0[(Y2) &0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \ - RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \ - \ - X3 = *RK++ ^ RT0[(Y3) &0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \ - RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \ +#define AES_FROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ + { \ + X0 = *RK++ ^ FT0[(Y0) & 0xFF] ^ FT1[(Y1 >> 8) & 0xFF] ^ \ + FT2[(Y2 >> 16) & 0xFF] ^ FT3[(Y3 >> 24) & 0xFF]; \ + \ + X1 = *RK++ ^ FT0[(Y1) & 0xFF] ^ FT1[(Y2 >> 8) & 0xFF] ^ \ + FT2[(Y3 >> 16) & 0xFF] ^ FT3[(Y0 >> 24) & 0xFF]; \ + \ + X2 = *RK++ ^ FT0[(Y2) & 0xFF] ^ FT1[(Y3 >> 8) & 0xFF] ^ \ + FT2[(Y0 >> 16) & 0xFF] ^ FT3[(Y1 >> 24) & 0xFF]; \ + \ + X3 = *RK++ ^ FT0[(Y3) & 0xFF] ^ FT1[(Y0 >> 8) & 0xFF] ^ \ + FT2[(Y1 >> 16) & 0xFF] ^ FT3[(Y2 >> 24) & 0xFF]; \ + } + +#define AES_RROUND(X0, X1, X2, X3, Y0, Y1, Y2, Y3) \ + { \ + X0 = *RK++ ^ RT0[(Y0) & 0xFF] ^ RT1[(Y3 >> 8) & 0xFF] ^ \ + RT2[(Y2 >> 16) & 0xFF] ^ RT3[(Y1 >> 24) & 0xFF]; \ + \ + X1 = *RK++ ^ RT0[(Y1) & 0xFF] ^ RT1[(Y0 >> 8) & 0xFF] ^ \ + RT2[(Y3 >> 16) & 0xFF] ^ RT3[(Y2 >> 24) & 0xFF]; \ + \ + X2 = *RK++ ^ RT0[(Y2) & 0xFF] ^ RT1[(Y1 >> 8) & 0xFF] ^ \ + RT2[(Y0 >> 16) & 0xFF] ^ RT3[(Y3 >> 24) & 0xFF]; \ + \ + X3 = *RK++ ^ RT0[(Y3) & 0xFF] ^ RT1[(Y2 >> 8) & 0xFF] ^ \ + RT2[(Y1 >> 16) & 0xFF] ^ RT3[(Y0 >> 24) & 0xFF]; \ } /* @@ -8366,7 +8561,8 @@ void aes_init_keygen_tables(void) { * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits). * ******************************************************************************/ -static int aes_set_encryption_key(aes_context *ctx, const uchar *key, uint keysize) { +static int aes_set_encryption_key(aes_context *ctx, const uchar *key, + uint keysize) { uint i; // general purpose iteration local uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer @@ -8443,7 +8639,8 @@ static int aes_set_encryption_key(aes_context *ctx, const uchar *key, uint keysi * length in bits. Valid lengths are: 128, 192, or 256 bits. * ******************************************************************************/ -static int aes_set_decryption_key(aes_context *ctx, const uchar *key, uint keysize) { +static int aes_set_decryption_key(aes_context *ctx, const uchar *key, + uint keysize) { int i, j; aes_context cty; // a calling aes context for set_encryption_key uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer @@ -8479,10 +8676,10 @@ static int aes_set_decryption_key(aes_context *ctx, const uchar *key, uint keysi * Invoked to establish the key schedule for subsequent encryption/decryption * ******************************************************************************/ -int aes_setkey(aes_context *ctx, // AES context provided by our caller - int mode, // ENCRYPT or DECRYPT flag - const uchar *key, // pointer to the key - uint keysize) // key length in bytes +static int aes_setkey(aes_context *ctx, // AES context provided by our caller + int mode, // ENCRYPT or DECRYPT flag + const uchar *key, // pointer to the key + uint keysize) // key length in bytes { // since table initialization is not thread safe, we could either add // system-specific mutexes and init the AES key generation tables on @@ -8525,7 +8722,8 @@ int aes_setkey(aes_context *ctx, // AES context provided by our caller * and all keying information appropriate for the task. * ******************************************************************************/ -int aes_cipher(aes_context *ctx, const uchar input[16], uchar output[16]) { +static int aes_cipher(aes_context *ctx, const uchar input[16], + uchar output[16]) { int i; uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals @@ -8550,22 +8748,22 @@ int aes_cipher(aes_context *ctx, const uchar input[16], uchar output[16]) { AES_RROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - X0 = *RK++ ^ ((uint32_t) RSb[(Y0) &0xFF]) ^ + X0 = *RK++ ^ ((uint32_t) RSb[(Y0) & 0xFF]) ^ ((uint32_t) RSb[(Y3 >> 8) & 0xFF] << 8) ^ ((uint32_t) RSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t) RSb[(Y1 >> 24) & 0xFF] << 24); - X1 = *RK++ ^ ((uint32_t) RSb[(Y1) &0xFF]) ^ + X1 = *RK++ ^ ((uint32_t) RSb[(Y1) & 0xFF]) ^ ((uint32_t) RSb[(Y0 >> 8) & 0xFF] << 8) ^ ((uint32_t) RSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t) RSb[(Y2 >> 24) & 0xFF] << 24); - X2 = *RK++ ^ ((uint32_t) RSb[(Y2) &0xFF]) ^ + X2 = *RK++ ^ ((uint32_t) RSb[(Y2) & 0xFF]) ^ ((uint32_t) RSb[(Y1 >> 8) & 0xFF] << 8) ^ ((uint32_t) RSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t) RSb[(Y3 >> 24) & 0xFF] << 24); - X3 = *RK++ ^ ((uint32_t) RSb[(Y3) &0xFF]) ^ + X3 = *RK++ ^ ((uint32_t) RSb[(Y3) & 0xFF]) ^ ((uint32_t) RSb[(Y2 >> 8) & 0xFF] << 8) ^ ((uint32_t) RSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t) RSb[(Y0 >> 24) & 0xFF] << 24); @@ -8580,22 +8778,22 @@ int aes_cipher(aes_context *ctx, const uchar input[16], uchar output[16]) { AES_FROUND(Y0, Y1, Y2, Y3, X0, X1, X2, X3); - X0 = *RK++ ^ ((uint32_t) FSb[(Y0) &0xFF]) ^ + X0 = *RK++ ^ ((uint32_t) FSb[(Y0) & 0xFF]) ^ ((uint32_t) FSb[(Y1 >> 8) & 0xFF] << 8) ^ ((uint32_t) FSb[(Y2 >> 16) & 0xFF] << 16) ^ ((uint32_t) FSb[(Y3 >> 24) & 0xFF] << 24); - X1 = *RK++ ^ ((uint32_t) FSb[(Y1) &0xFF]) ^ + X1 = *RK++ ^ ((uint32_t) FSb[(Y1) & 0xFF]) ^ ((uint32_t) FSb[(Y2 >> 8) & 0xFF] << 8) ^ ((uint32_t) FSb[(Y3 >> 16) & 0xFF] << 16) ^ ((uint32_t) FSb[(Y0 >> 24) & 0xFF] << 24); - X2 = *RK++ ^ ((uint32_t) FSb[(Y2) &0xFF]) ^ + X2 = *RK++ ^ ((uint32_t) FSb[(Y2) & 0xFF]) ^ ((uint32_t) FSb[(Y3 >> 8) & 0xFF] << 8) ^ ((uint32_t) FSb[(Y0 >> 16) & 0xFF] << 16) ^ ((uint32_t) FSb[(Y1 >> 24) & 0xFF] << 24); - X3 = *RK++ ^ ((uint32_t) FSb[(Y3) &0xFF]) ^ + X3 = *RK++ ^ ((uint32_t) FSb[(Y3) & 0xFF]) ^ ((uint32_t) FSb[(Y0 >> 8) & 0xFF] << 8) ^ ((uint32_t) FSb[(Y1 >> 16) & 0xFF] << 16) ^ ((uint32_t) FSb[(Y2 >> 24) & 0xFF] << 24); @@ -8636,7 +8834,6 @@ int aes_cipher(aes_context *ctx, const uchar input[16], uchar output[16]) { * *******************************************************************************/ - /****************************************************************************** * ==== IMPLEMENTATION WARNING ==== * @@ -8721,7 +8918,7 @@ static const uint64_t last4[16] = { * environment is running. * ******************************************************************************/ -int gcm_initialize(void) { +int mg_gcm_initialize(void) { aes_init_keygen_tables(); return (0); } @@ -8781,10 +8978,11 @@ static void gcm_mult(gcm_context *ctx, // pointer to established context * and populates the gcm context's pre-calculated HTables. * ******************************************************************************/ -int gcm_setkey(gcm_context *ctx, // pointer to caller-provided gcm context - const uchar *key, // pointer to the AES encryption key - const uint keysize) // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) +static int gcm_setkey( + gcm_context *ctx, // pointer to caller-provided gcm context + const uchar *key, // pointer to the AES encryption key + const uint keysize) // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) { int ret, i, j; uint64_t hi, lo; @@ -8873,7 +9071,7 @@ int gcm_start(gcm_context *ctx, // pointer to user-provided GCM context ctx->len = 0; ctx->add_len = 0; - ctx->mode = mode; // set the GCM encryption/decryption mode + ctx->mode = mode; // set the GCM encryption/decryption mode ctx->aes_ctx.mode = MG_ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode if (iv_len == 12) { // GCM natively uses a 12-byte, 96-bit IV @@ -9051,51 +9249,6 @@ int gcm_crypt_and_tag( return (0); } -/****************************************************************************** - * - * GCM_AUTH_DECRYPT - * - * This DECRYPTS a user-provided data buffer with optional associated data. - * It then verifies a user-supplied authentication tag against the tag just - * re-created during decryption to verify that the data has not been altered. - * - * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption - * and authentication tag generation. - * - ******************************************************************************/ -int gcm_auth_decrypt( - gcm_context *ctx, // gcm context with key already setup - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - const uchar *tag, // pointer to the tag to be authenticated - size_t tag_len) // byte length of the tag <= 16 -{ - uchar check_tag[16]; // the tag generated and returned by decryption - int diff; // an ORed flag to detect authentication errors - size_t i; // our local iterator - /* - we use GCM_DECRYPT_AND_TAG (above) to perform our decryption - (which is an identical XORing to reverse the previous one) - and also to re-generate the matching authentication tag - */ - gcm_crypt_and_tag(ctx, MG_DECRYPT, iv, iv_len, add, add_len, input, output, - length, check_tag, tag_len); - - // now we verify the authentication tag in 'constant time' - for (diff = 0, i = 0; i < tag_len; i++) diff |= tag[i] ^ check_tag[i]; - - if (diff != 0) { // see whether any bits differed? - memset(output, 0, length); // if so... wipe the output data - return (GCM_AUTH_FAILURE); // return GCM_AUTH_FAILURE - } - return (0); -} - /****************************************************************************** * * GCM_ZERO_CTX @@ -9117,36 +9270,36 @@ void gcm_zero_ctx(gcm_context *ctx) { // // -int aes_gcm_encrypt(unsigned char *output, // - const unsigned char *input, size_t input_length, - const unsigned char *key, const size_t key_len, - const unsigned char *iv, const size_t iv_len, - unsigned char *aead, size_t aead_len, unsigned char *tag, - const size_t tag_len) { +int mg_aes_gcm_encrypt(unsigned char *output, // + const unsigned char *input, size_t input_length, + const unsigned char *key, const size_t key_len, + const unsigned char *iv, const size_t iv_len, + unsigned char *aead, size_t aead_len, unsigned char *tag, + const size_t tag_len) { int ret = 0; // our return value gcm_context ctx; // includes the AES context structure - gcm_setkey(&ctx, key, (const uint) key_len); + gcm_setkey(&ctx, key, (uint) key_len); - ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead, aead_len, input, output, - input_length, tag, tag_len); + ret = gcm_crypt_and_tag(&ctx, MG_ENCRYPT, iv, iv_len, aead, aead_len, input, + output, input_length, tag, tag_len); gcm_zero_ctx(&ctx); return (ret); } -int aes_gcm_decrypt(unsigned char *output, const unsigned char *input, - size_t input_length, const unsigned char *key, - const size_t key_len, const unsigned char *iv, - const size_t iv_len) { +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input, + size_t input_length, const unsigned char *key, + const size_t key_len, const unsigned char *iv, + const size_t iv_len) { int ret = 0; // our return value gcm_context ctx; // includes the AES context structure size_t tag_len = 0; unsigned char *tag_buf = NULL; - gcm_setkey(&ctx, key, (const uint) key_len); + gcm_setkey(&ctx, key, (uint) key_len); ret = gcm_crypt_and_tag(&ctx, MG_DECRYPT, iv, iv_len, NULL, 0, input, output, input_length, tag_buf, tag_len); @@ -9163,34 +9316,71 @@ int aes_gcm_decrypt(unsigned char *output, const unsigned char *input, #endif + + #if MG_TLS == MG_TLS_BUILTIN -// handshake is re-entrant, so we need to keep track of its state +/* TLS 1.3 Record Content Type (RFC8446 B.1) */ +#define MG_TLS_CHANGE_CIPHER 20 +#define MG_TLS_ALERT 21 +#define MG_TLS_HANDSHAKE 22 +#define MG_TLS_APP_DATA 23 +#define MG_TLS_HEARTBEAT 24 + +/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */ +#define MG_TLS_CLIENT_HELLO 1 +#define MG_TLS_SERVER_HELLO 2 +#define MG_TLS_ENCRYPTED_EXTENSIONS 8 +#define MG_TLS_CERTIFICATE 11 +#define MG_TLS_CERTIFICATE_VERIFY 15 +#define MG_TLS_FINISHED 20 + +// handshake is re-entrant, so we need to keep track of its state state names +// refer to RFC8446#A.1 enum mg_tls_hs_state { - MG_TLS_HS_CLIENT_HELLO, // first, wait for ClientHello - MG_TLS_HS_SERVER_HELLO, // then, send all server handshake data at once - MG_TLS_HS_CLIENT_CHANGE_CIPHER, // finally wait for ClientChangeCipher - MG_TLS_HS_CLIENT_FINISH, // and ClientFinish (encrypted) - MG_TLS_HS_DONE, // finish handshake, start application data flow + // Client state machine: + MG_TLS_STATE_CLIENT_START, // Send ClientHello + MG_TLS_STATE_CLIENT_WAIT_SH, // Wait for ServerHello + MG_TLS_STATE_CLIENT_WAIT_EE, // Wait for EncryptedExtensions + MG_TLS_STATE_CLIENT_WAIT_CERT, // Wait for Certificate + MG_TLS_STATE_CLIENT_WAIT_CV, // Wait for CertificateVerify + MG_TLS_STATE_CLIENT_WAIT_FINISHED, // Wait for Finished + MG_TLS_STATE_CLIENT_CONNECTED, // Done + + // Server state machine: + MG_TLS_STATE_SERVER_START, // Wait for ClientHello + MG_TLS_STATE_SERVER_NEGOTIATED, // Wait for Finished + MG_TLS_STATE_SERVER_CONNECTED // Done }; // per-connection TLS data struct tls_data { enum mg_tls_hs_state state; // keep track of connection handshake progress - struct mg_iobuf send; // For the receive path, we're reusing c->rtls + struct mg_iobuf send; // For the receive path, we're reusing c->rtls + struct mg_iobuf recv; // While c->rtls contains full records, recv reuses + // the same underlying buffer but points at individual + // decrypted messages + uint8_t content_type; // Last received record content type mg_sha256_ctx sha256; // incremental SHA-256 hash for TLS handshake uint32_t sseq; // server sequence number, used in encryption uint32_t cseq; // client sequence number, used in decryption + uint8_t random[32]; // client random from ClientHello uint8_t session_id[32]; // client session ID between the handshake states uint8_t x25519_cli[32]; // client X25519 key between the handshake states uint8_t x25519_sec[32]; // x25519 secret between the handshake states + int skip_verification; // perform checks on server certificate? struct mg_str server_cert_der; // server certificate in DER format uint8_t server_key[32]; // server EC private key + char hostname[254]; // server hostname (client extension) + + uint8_t certhash[32]; // certificate message hash + uint8_t pubkey[64]; // server EC public key to verify cert + uint8_t sighash[32]; // server EC public key to verify cert // keys for AES encryption uint8_t handshake_secret[32]; @@ -9203,256 +9393,143 @@ struct tls_data { }; #define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) | MG_U8P(p)[1])) -#define TLS_HDR_SIZE 5 // 1 byte type, 2 bytes version, 2 bytes len - -// for derived tls keys we need SHA256([0]*32) -static uint8_t zeros[32] = {0}; -static uint8_t zeros_sha256_digest[32] = - "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24" - "\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55"; - -#define X25519_BYTES 32 -const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9}; - -#define X25519_WBITS 32 - -typedef uint32_t limb_t; -typedef uint64_t dlimb_t; -typedef int64_t sdlimb_t; -#define LIMB(x) (uint32_t)(x##ull), (uint32_t) ((x##ull) >> 32) +#define MG_LOAD_BE24(p) \ + ((uint32_t) ((MG_U8P(p)[0] << 16U) | (MG_U8P(p)[1] << 8U) | MG_U8P(p)[2])) +#define MG_STORE_BE16(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 8U) & 255; \ + MG_U8P(p)[1] = (n) & 255; \ + } while (0) -#define NLIMBS (256 / X25519_WBITS) -typedef limb_t fe[NLIMBS]; +#define TLS_RECHDR_SIZE 5 // 1 byte type, 2 bytes version, 2 bytes length +#define TLS_MSGHDR_SIZE 4 // 1 byte type, 3 bytes length -static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) { - dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry; - *carry = (limb_t) (tmp >> X25519_WBITS); - return (limb_t) tmp; +#if 1 +static void mg_ssl_key_log(const char *label, uint8_t client_random[32], + uint8_t *secret, size_t secretsz) { + (void) label; + (void) client_random; + (void) secret; + (void) secretsz; } - -// These functions are implemented in terms of umaal on ARM -static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) { - dlimb_t total = (dlimb_t) *carry + acc + mand; - *carry = (limb_t) (total >> X25519_WBITS); - return (limb_t) total; +#else +#include +static void mg_ssl_key_log(const char *label, uint8_t client_random[32], + uint8_t *secret, size_t secretsz) { + char *keylogfile = getenv("SSLKEYLOGFILE"); + if (keylogfile == NULL) { + return; + } + FILE *f = fopen(keylogfile, "a"); + fprintf(f, "%s ", label); + for (int i = 0; i < 32; i++) { + fprintf(f, "%02x", client_random[i]); + } + fprintf(f, " "); + for (unsigned int i = 0; i < secretsz; i++) { + fprintf(f, "%02x", secret[i]); + } + fprintf(f, "\n"); + fclose(f); } +#endif -static limb_t adc0(limb_t *carry, limb_t acc) { - dlimb_t total = (dlimb_t) *carry + acc; - *carry = (limb_t) (total >> X25519_WBITS); - return (limb_t) total; +// for derived tls keys we need SHA256([0]*32) +static uint8_t zeros[32] = {0}; +static uint8_t zeros_sha256_digest[32] = { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, + 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, + 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + +// helper to hexdump buffers inline +static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) { + char p[8 * 4096]; + MG_VERBOSE(("%s: %s", msg, mg_hex(buf, bufsz, p))); } -// - Precondition: carry is small. -// - Invariant: result of propagate is < 2^255 + 1 word -// - In particular, always less than 2p. -// - Also, output x >= min(x,19) -static void propagate(fe x, limb_t over) { - unsigned i; - limb_t carry; - over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1; - x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1)); +// helper utilities to parse ASN.1 DER +struct mg_der_tlv { + uint8_t type; + uint32_t len; + uint8_t *value; +}; - carry = over * 19; - for (i = 0; i < NLIMBS; i++) { - x[i] = adc0(&carry, x[i]); +// parse DER into a TLV record +static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) { + if (dersz < 2) { + return -1; } -} - -static void add(fe out, const fe a, const fe b) { - unsigned i; - limb_t carry = 0; - for (i = 0; i < NLIMBS; i++) { - out[i] = adc(&carry, a[i], b[i]); + tlv->type = der[0]; + tlv->len = der[1]; + tlv->value = der + 2; + if (tlv->len > 0x7f) { + uint32_t i, n = tlv->len - 0x80; + tlv->len = 0; + for (i = 0; i < n; i++) { + tlv->len = (tlv->len << 8) | (der[2 + i]); + } + tlv->value = der + 2 + n; } - propagate(out, carry); -} - -static void sub(fe out, const fe a, const fe b) { - unsigned i; - sdlimb_t carry = -38; - for (i = 0; i < NLIMBS; i++) { - carry = carry + a[i] - b[i]; - out[i] = (limb_t) carry; - carry >>= X25519_WBITS; + if (der + dersz < tlv->value + tlv->len) { + return -1; } - propagate(out, (limb_t) (1 + carry)); + return 0; } -// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of `fe` -// to avoid build warnings -static void mul(fe out, const fe a, const limb_t *b, unsigned nb) { - limb_t accum[2 * NLIMBS] = {0}; - unsigned i, j; - - limb_t carry2; - for (i = 0; i < nb; i++) { - limb_t mand = b[i]; - carry2 = 0; - for (j = 0; j < NLIMBS; j++) { - accum[i + j] = umaal(&carry2, accum[i + j], mand, a[j]); - } - accum[i + j] = carry2; +static int mg_der_find(uint8_t *der, size_t dersz, uint8_t *oid, size_t oidsz, + struct mg_der_tlv *tlv) { + uint8_t *p, *end; + struct mg_der_tlv child = {0, 0, NULL}; + if (mg_der_to_tlv(der, dersz, tlv) < 0) { + return -1; // invalid DER + } else if (tlv->type == 6) { // found OID, check value + return (tlv->len == oidsz && memcmp(tlv->value, oid, oidsz) == 0); + } else if ((tlv->type & 0x20) == 0) { + return 0; // Primitive, but not OID: not found } - - carry2 = 0; - for (j = 0; j < NLIMBS; j++) { - out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]); + // Constructed object: scan children + p = tlv->value; + end = tlv->value + tlv->len; + while (end > p) { + int r; + mg_der_to_tlv(p, (size_t) (end - p), &child); + r = mg_der_find(p, (size_t) (end - p), oid, oidsz, tlv); + if (r < 0) return -1; // error + if (r > 0) return 1; // found OID! + p = child.value + child.len; } - propagate(out, carry2); + return 0; // not found } -static void sqr(fe out, const fe a) { - mul(out, a, a, NLIMBS); -} -static void mul1(fe out, const fe a) { - mul(out, a, out, NLIMBS); -} -static void sqr1(fe a) { - mul1(a, a); +// Did we receive a full TLS record in the c->rtls buffer? +static bool mg_tls_got_record(struct mg_connection *c) { + return c->rtls.len >= (size_t) TLS_RECHDR_SIZE && + c->rtls.len >= + (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3)); } -static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS], - limb_t doswap) { - unsigned i; - for (i = 0; i < 2 * NLIMBS; i++) { - limb_t xor = (a[i] ^ b[i]) & doswap; - a[i] ^= xor; - b[i] ^= xor; - } +// Remove a single TLS record from the recv buffer +static void mg_tls_drop_record(struct mg_connection *c) { + struct mg_iobuf *rio = &c->rtls; + uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE; + mg_iobuf_del(rio, 0, n); } -// Canonicalize a field element x, reducing it to the least residue which is -// congruent to it mod 2^255-19 -// - Precondition: x < 2^255 + 1 word -static limb_t canon(fe x) { - // First, add 19. - unsigned i; - limb_t carry0 = 19; - limb_t res; - sdlimb_t carry; - for (i = 0; i < NLIMBS; i++) { - x[i] = adc0(&carry0, x[i]); - } - propagate(x, carry0); - - // Here, 19 <= x2 < 2^255 - // - This is because we added 19, so before propagate it can't be less - // than 19. After propagate, it still can't be less than 19, because if - // propagate does anything it adds 19. - // - We know that the high bit must be clear, because either the input was ~ - // 2^255 + one word + 19 (in which case it propagates to at most 2 words) or - // it was < 2^255. So now, if we subtract 19, we will get back to something in - // [0,2^255-19). - carry = -19; - res = 0; - for (i = 0; i < NLIMBS; i++) { - carry += x[i]; - res |= x[i] = (limb_t) carry; - carry >>= X25519_WBITS; - } - return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS); -} - -static const limb_t a24[1] = {121665}; - -static void ladder_part1(fe xs[5]) { - limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; - add(t1, x2, z2); // t1 = A - sub(z2, x2, z2); // z2 = B - add(x2, x3, z3); // x2 = C - sub(z3, x3, z3); // z3 = D - mul1(z3, t1); // z3 = DA - mul1(x2, z2); // x3 = BC - add(x3, z3, x2); // x3 = DA+CB - sub(z3, z3, x2); // z3 = DA-CB - sqr1(t1); // t1 = AA - sqr1(z2); // z2 = BB - sub(x2, t1, z2); // x2 = E = AA-BB - mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24 - add(z2, z2, t1); // z2 = E*a24 + AA -} - -static void ladder_part2(fe xs[5], const fe x1) { - limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; - sqr1(z3); // z3 = (DA-CB)^2 - mul1(z3, x1); // z3 = x1 * (DA-CB)^2 - sqr1(x3); // x3 = (DA+CB)^2 - mul1(z2, x2); // z2 = AA*(E*a24+AA) - sub(x2, t1, x2); // x2 = BB again - mul1(x2, t1); // x2 = AA*BB -} - -static void x25519_core(fe xs[5], const uint8_t scalar[X25519_BYTES], - const uint8_t *x1, int clamp) { - int i; - limb_t swap = 0; - limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3]; - memset(xs, 0, 4 * sizeof(fe)); - x2[0] = z3[0] = 1; - memcpy(x3, x1, sizeof(fe)); - - for (i = 255; i >= 0; i--) { - uint8_t bytei = scalar[i / 8]; - limb_t doswap; - if (clamp) { - if (i / 8 == 0) { - bytei &= (uint8_t) ~7U; - } else if (i / 8 == X25519_BYTES - 1) { - bytei &= 0x7F; - bytei |= 0x40; - } - } - doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1); - condswap(x2, x3, swap ^ doswap); - swap = doswap; - - ladder_part1(xs); - ladder_part2(xs, (const limb_t *) x1); +// Remove a single TLS message from decrypted buffer, remove the wrapping +// record if it was the last message within a record +static void mg_tls_drop_message(struct mg_connection *c) { + uint32_t len; + struct tls_data *tls = (struct tls_data *) c->tls; + if (tls->recv.len == 0) { + return; } - condswap(x2, x3, swap); -} - -static int x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES], - const uint8_t x1[X25519_BYTES], int clamp) { - int i, ret; - fe xs[5]; - limb_t *x2, *z2, *z3, *prev; - static const struct { - uint8_t a, c, n; - } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3, 1, 1}, - {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125}, - {3, 1, 2}, {3, 1, 2}, {3, 1, 1}}; - x25519_core(xs, scalar, x1, clamp); - - // Precomputed inversion chain - x2 = xs[0]; - z2 = xs[1]; - z3 = xs[3]; - - prev = z2; - for (i = 0; i < 13; i++) { - int j; - limb_t *a = xs[steps[i].a]; - for (j = steps[i].n; j > 0; j--) { - sqr(a, prev); - prev = a; - } - mul1(a, xs[steps[i].c]); + len = MG_LOAD_BE24(tls->recv.buf + 1); + mg_sha256_update(&tls->sha256, tls->recv.buf, len + TLS_MSGHDR_SIZE); + tls->recv.buf += len + TLS_MSGHDR_SIZE; + tls->recv.len -= len + TLS_MSGHDR_SIZE; + if (tls->recv.len == 0) { + mg_tls_drop_record(c); } - - // Here prev = z3 - // x2 /= z2 - mul((limb_t *) out, x2, z3, NLIMBS); - ret = (int) canon((limb_t *) out); - if (!clamp) ret = 0; - return ret; -} - -// helper to hexdump buffers inline -static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) { - char p[512]; - MG_VERBOSE(("%s: %s", msg, mg_hex(buf, bufsz, p))); } // TLS1.3 secret derivation based on the key label @@ -9463,136 +9540,19 @@ static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz, uint8_t secret[32]; uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz}; // TODO: assert lengths of label, key, data and hash - memmove(packed + 3, label, labelsz); + if (labelsz > 0) memmove(packed + 3, label, labelsz); packed[3 + labelsz] = (uint8_t) datasz; - memmove(packed + labelsz + 4, data, datasz); + if (datasz > 0) memmove(packed + labelsz + 4, data, datasz); packed[4 + labelsz + datasz] = 1; mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz); memmove(hash, secret, hashsz); } -// Did we receive a full TLS message in the c->rtls buffer? -static bool mg_tls_got_msg(struct mg_connection *c) { - return c->rtls.len >= (size_t) TLS_HDR_SIZE && - c->rtls.len >= (size_t) (TLS_HDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3)); -} - -// Remove a single TLS record from the recv buffer -static void mg_tls_drop_packet(struct mg_iobuf *rio) { - uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_HDR_SIZE; - mg_iobuf_del(rio, 0, n); -} - -// read and parse ClientHello record -static int mg_tls_client_hello(struct mg_connection *c) { - struct tls_data *tls = c->tls; - struct mg_iobuf *rio = &c->rtls; - uint8_t session_id_len; - uint16_t j; - uint16_t cipher_suites_len; - uint16_t ext_len; - uint8_t *ext; - - if (!mg_tls_got_msg(c)) { - return MG_IO_WAIT; - } - if (rio->buf[0] != 0x16 || rio->buf[5] != 0x01) { - mg_error(c, "not a hello packet"); - return -1; - } - mg_sha256_update(&tls->sha256, rio->buf + 5, rio->len - 5); - session_id_len = rio->buf[43]; - if (session_id_len == sizeof(tls->session_id)) { - memmove(tls->session_id, rio->buf + 44, session_id_len); - } else if (session_id_len != 0) { - MG_INFO(("bad session id len")); - } - cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len); - ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len); - ext = rio->buf + 50 + session_id_len + cipher_suites_len; - for (j = 0; j < ext_len;) { - uint16_t k; - uint16_t key_exchange_len; - uint8_t *key_exchange; - uint16_t n = MG_LOAD_BE16(ext + j + 2); - if (ext[j] != 0x00 || - ext[j + 1] != 0x33) { // not a key share extension, ignore - j += (uint16_t) (n + 4); - continue; - } - key_exchange_len = MG_LOAD_BE16(ext + j + 5); - key_exchange = ext + j + 6; - for (k = 0; k < key_exchange_len;) { - uint16_t m = MG_LOAD_BE16(key_exchange + k + 2); - if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) { - memmove(tls->x25519_cli, key_exchange + k + 4, m); - mg_tls_drop_packet(rio); - return 0; - } - k += (uint16_t) (m + 4); - } - j += (uint16_t) (n + 4); - } - mg_error(c, "bad client hello"); - return -1; -} - -// put ServerHello record into wio buffer -static void mg_tls_server_hello(struct mg_connection *c) { - struct tls_data *tls = c->tls; - struct mg_iobuf *wio = &tls->send; - - uint8_t msg_server_hello[122] = - // server hello, tls 1.2 - "\x02\x00\x00\x76\x03\x03" - // random (32 bytes) - "\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe" - "\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe" - // session ID length + session ID (32 bytes) - "\x20" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" -#if defined(CHACHA20) && CHACHA20 - // TLS_CHACHA20_POLY1305_SHA256 + no compression - "\x13\x03\x00" -#else - // TLS_AES_128_GCM_SHA256 + no compression - "\x13\x01\x00" -#endif - // extensions + keyshare - "\x00\x2e\x00\x33\x00\x24\x00\x1d\x00\x20" - // x25519 keyshare - "\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab" - "\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab" - // supported versions (tls1.3 == 0x304) - "\x00\x2b\x00\x02\x03\x04"; - - // calculate keyshare - uint8_t x25519_pub[X25519_BYTES]; - uint8_t x25519_prv[X25519_BYTES]; - mg_random(x25519_prv, sizeof(x25519_prv)); - x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1); - x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1); - mg_tls_hexdump("x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec)); - - // fill in the gaps: session ID + keyshare - memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id)); - memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub)); - - // server hello message - mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5); - mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello)); - mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello)); - - // change cipher message - mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6); -} - // at this point we have x25519 shared secret, we can generate a set of derived // handshake encryption keys static void mg_tls_generate_handshake_keys(struct mg_connection *c) { - struct tls_data *tls = c->tls; + struct tls_data *tls = (struct tls_data *) c->tls; mg_sha256_ctx sha256; uint8_t early_secret[32]; @@ -9614,6 +9574,7 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) { memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); mg_sha256_final(hello_hash, &sha256); + mg_tls_hexdump("hello hash", hello_hash, 32); // derive keys needed for the rest of the handshake mg_tls_derive_secret("tls13 s hs traffic", tls->handshake_secret, 32, hello_hash, 32, server_hs_secret, 32); @@ -9623,7 +9584,6 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) { tls->server_write_iv, 12); mg_tls_derive_secret("tls13 finished", server_hs_secret, 32, NULL, 0, tls->server_finished_key, 32); - mg_tls_hexdump("s hs traffic", server_hs_secret, 32); mg_tls_derive_secret("tls13 c hs traffic", tls->handshake_secret, 32, hello_hash, 32, client_hs_secret, 32); @@ -9633,98 +9593,343 @@ static void mg_tls_generate_handshake_keys(struct mg_connection *c) { tls->client_write_iv, 12); mg_tls_derive_secret("tls13 finished", client_hs_secret, 32, NULL, 0, tls->client_finished_key, 32); + + mg_tls_hexdump("s hs traffic", server_hs_secret, 32); + mg_tls_hexdump("s key", tls->server_write_key, 16); + mg_tls_hexdump("s iv", tls->server_write_iv, 12); + mg_tls_hexdump("s finished", tls->server_finished_key, 32); + mg_tls_hexdump("c hs traffic", client_hs_secret, 32); + mg_tls_hexdump("c key", tls->client_write_key, 16); + mg_tls_hexdump("c iv", tls->client_write_iv, 16); + mg_tls_hexdump("c finished", tls->client_finished_key, 32); + + mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random, + server_hs_secret, 32); + mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random, + client_hs_secret, 32); +} + +static void mg_tls_generate_application_keys(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + uint8_t hash[32]; + uint8_t premaster_secret[32]; + uint8_t master_secret[32]; + uint8_t server_secret[32]; + uint8_t client_secret[32]; + + mg_sha256_ctx sha256; + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); + mg_sha256_final(hash, &sha256); + + mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32, + zeros_sha256_digest, 32, premaster_secret, 32); + mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32); + + mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32, + server_secret, 32); + mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0, + tls->server_write_key, 16); + mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0, + tls->server_write_iv, 12); + mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32, + client_secret, 32); + mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0, + tls->client_write_key, 16); + mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0, + tls->client_write_iv, 12); + + mg_tls_hexdump("s ap traffic", server_secret, 32); + mg_tls_hexdump("s key", tls->server_write_key, 16); + mg_tls_hexdump("s iv", tls->server_write_iv, 12); + mg_tls_hexdump("s finished", tls->server_finished_key, 32); + mg_tls_hexdump("c ap traffic", client_secret, 32); + mg_tls_hexdump("c key", tls->client_write_key, 16); + mg_tls_hexdump("c iv", tls->client_write_iv, 16); + mg_tls_hexdump("c finished", tls->client_finished_key, 32); + tls->sseq = tls->cseq = 0; + + mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random, server_secret, 32); + mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random, client_secret, 32); } // AES GCM encryption of the message + put encoded data into the write buffer static void mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg, size_t msgsz, uint8_t msgtype) { - struct tls_data *tls = c->tls; + struct tls_data *tls = (struct tls_data *) c->tls; struct mg_iobuf *wio = &tls->send; uint8_t *outmsg; uint8_t *tag; size_t encsz = msgsz + 16 + 1; - uint8_t hdr[5] = {0x17, 0x03, 0x03, (encsz >> 8) & 0xff, encsz & 0xff}; - uint8_t associated_data[5] = {0x17, 0x03, 0x03, (encsz >> 8) & 0xff, - encsz & 0xff}; + uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03, + (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)}; + uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03, + (uint8_t) ((encsz >> 8) & 0xff), + (uint8_t) (encsz & 0xff)}; uint8_t nonce[12]; - memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv)); - nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U); - nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U); - nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U); - nonce[11] ^= (uint8_t) ((tls->sseq) & 255U); - gcm_initialize(); + mg_gcm_initialize(); + + if (c->is_client) { + memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv)); + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U); + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U); + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U); + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U); + } else { + memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv)); + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U); + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U); + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U); + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U); + } + mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr)); mg_iobuf_resize(wio, wio->len + encsz); outmsg = wio->buf + wio->len; tag = wio->buf + wio->len + msgsz + 1; memmove(outmsg, msg, msgsz); outmsg[msgsz] = msgtype; - aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->server_write_key, - sizeof(tls->server_write_key), nonce, sizeof(nonce), - associated_data, sizeof(associated_data), tag, 16); + if (c->is_client) { + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->client_write_key, + sizeof(tls->client_write_key), nonce, sizeof(nonce), + associated_data, sizeof(associated_data), tag, 16); + tls->cseq++; + } else { + mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, tls->server_write_key, + sizeof(tls->server_write_key), nonce, sizeof(nonce), + associated_data, sizeof(associated_data), tag, 16); + tls->sseq++; + } wio->len += encsz; - tls->sseq++; } -// read an encrypted message, decrypt it into read buffer (AES GCM) -static int mg_tls_recv_decrypt(struct mg_connection *c, void *buf, - size_t bufsz) { - struct tls_data *tls = c->tls; +// read an encrypted record, decrypt it in place +static int mg_tls_recv_record(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; struct mg_iobuf *rio = &c->rtls; - // struct mg_iobuf *rio = &tls->recv; uint16_t msgsz; uint8_t *msg; uint8_t nonce[12]; int r; + if (tls->recv.len > 0) { + return 0; /* some data from previous record is still present */ + } for (;;) { - if (!mg_tls_got_msg(c)) { + if (!mg_tls_got_record(c)) { return MG_IO_WAIT; } - if (rio->buf[0] == 0x17) { + if (rio->buf[0] == MG_TLS_APP_DATA) { break; - } else if (rio->buf[0] == 0x15) { - MG_INFO(("TLS ALERT packet received")); // TODO: drop packet? + } else if (rio->buf[0] == + MG_TLS_CHANGE_CIPHER) { // Skip ChangeCipher messages + mg_tls_drop_record(c); + } else if (rio->buf[0] == MG_TLS_ALERT) { // Skip Alerts + MG_INFO(("TLS ALERT packet received")); + mg_tls_drop_record(c); } else { mg_error(c, "unexpected packet"); return -1; } } + + mg_gcm_initialize(); msgsz = MG_LOAD_BE16(rio->buf + 3); msg = rio->buf + 5; - memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv)); - nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U); - nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U); - nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U); - nonce[11] ^= (uint8_t) ((tls->cseq) & 255U); - aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key, - sizeof(tls->client_write_key), nonce, sizeof(nonce)); - r = msgsz - 16 - 1; - if (msg[r] == 0x17) { - if (bufsz > 0) { - memmove(buf, msg, msgsz - 16); - } + if (c->is_client) { + memmove(nonce, tls->server_write_iv, sizeof(tls->server_write_iv)); + nonce[8] ^= (uint8_t) ((tls->sseq >> 24) & 255U); + nonce[9] ^= (uint8_t) ((tls->sseq >> 16) & 255U); + nonce[10] ^= (uint8_t) ((tls->sseq >> 8) & 255U); + nonce[11] ^= (uint8_t) ((tls->sseq) & 255U); + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->server_write_key, + sizeof(tls->server_write_key), nonce, sizeof(nonce)); + tls->sseq++; } else { - r = 0; + memmove(nonce, tls->client_write_iv, sizeof(tls->client_write_iv)); + nonce[8] ^= (uint8_t) ((tls->cseq >> 24) & 255U); + nonce[9] ^= (uint8_t) ((tls->cseq >> 16) & 255U); + nonce[10] ^= (uint8_t) ((tls->cseq >> 8) & 255U); + nonce[11] ^= (uint8_t) ((tls->cseq) & 255U); + mg_aes_gcm_decrypt(msg, msg, msgsz - 16, tls->client_write_key, + sizeof(tls->client_write_key), nonce, sizeof(nonce)); + tls->cseq++; } - tls->cseq++; - mg_tls_drop_packet(rio); + r = msgsz - 16 - 1; + tls->content_type = msg[msgsz - 16 - 1]; + tls->recv.buf = msg; + tls->recv.size = tls->recv.len = msgsz - 16 - 1; return r; } -static void mg_tls_server_extensions(struct mg_connection *c) { - struct tls_data *tls = c->tls; +static void mg_tls_calc_cert_verify_hash(struct mg_connection *c, + uint8_t hash[32]) { + struct tls_data *tls = (struct tls_data *) c->tls; + uint8_t sig_content[130] = { + " " + " " + "TLS 1.3, server CertificateVerify\0"}; + mg_sha256_ctx sha256; + memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); + mg_sha256_final(sig_content + 98, &sha256); + + mg_sha256_init(&sha256); + mg_sha256_update(&sha256, sig_content, sizeof(sig_content)); + mg_sha256_final(hash, &sha256); +} + +// read and parse ClientHello record +static int mg_tls_server_recv_hello(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + struct mg_iobuf *rio = &c->rtls; + uint8_t session_id_len; + uint16_t j; + uint16_t cipher_suites_len; + uint16_t ext_len; + uint8_t *ext; + uint16_t msgsz; + + if (!mg_tls_got_record(c)) { + return MG_IO_WAIT; + } + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) { + mg_error(c, "not a client hello packet"); + return -1; + } + msgsz = MG_LOAD_BE16(rio->buf + 3); + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz); + // store client random + memmove(tls->random, rio->buf + 11, sizeof(tls->random)); + // store session_id + session_id_len = rio->buf[43]; + if (session_id_len == sizeof(tls->session_id)) { + memmove(tls->session_id, rio->buf + 44, session_id_len); + } else if (session_id_len != 0) { + MG_INFO(("bad session id len")); + } + cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len); + ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len); + ext = rio->buf + 50 + session_id_len + cipher_suites_len; + for (j = 0; j < ext_len;) { + uint16_t k; + uint16_t key_exchange_len; + uint8_t *key_exchange; + uint16_t n = MG_LOAD_BE16(ext + j + 2); + if (ext[j] != 0x00 || + ext[j + 1] != 0x33) { // not a key share extension, ignore + j += (uint16_t) (n + 4); + continue; + } + key_exchange_len = MG_LOAD_BE16(ext + j + 5); + key_exchange = ext + j + 6; + for (k = 0; k < key_exchange_len;) { + uint16_t m = MG_LOAD_BE16(key_exchange + k + 2); + if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) { + memmove(tls->x25519_cli, key_exchange + k + 4, m); + mg_tls_drop_record(c); + return 0; + } + k += (uint16_t) (m + 4); + } + j += (uint16_t) (n + 4); + } + mg_error(c, "bad client hello"); + return -1; +} + +#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X' +#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B +#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B + +// put ServerHello record into wio buffer +static void mg_tls_server_send_hello(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + struct mg_iobuf *wio = &tls->send; + + uint8_t msg_server_hello[122] = { + // server hello, tls 1.2 + 0x02, + 0x00, + 0x00, + 0x76, + 0x03, + 0x03, + // random (32 bytes) + PLACEHOLDER_32B, + // session ID length + session ID (32 bytes) + 0x20, + PLACEHOLDER_32B, +#if defined(CHACHA20) && CHACHA20 + // TLS_CHACHA20_POLY1305_SHA256 + no compression + 0x13, + 0x03, + 0x00, +#else + // TLS_AES_128_GCM_SHA256 + no compression + 0x13, + 0x01, + 0x00, +#endif + // extensions + keyshare + 0x00, + 0x2e, + 0x00, + 0x33, + 0x00, + 0x24, + 0x00, + 0x1d, + 0x00, + 0x20, + // x25519 keyshare + PLACEHOLDER_32B, + // supported versions (tls1.3 == 0x304) + 0x00, + 0x2b, + 0x00, + 0x02, + 0x03, + 0x04 + }; + + // calculate keyshare + uint8_t x25519_pub[X25519_BYTES]; + uint8_t x25519_prv[X25519_BYTES]; + mg_random(x25519_prv, sizeof(x25519_prv)); + mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1); + mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1); + mg_tls_hexdump("s x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec)); + + // fill in the gaps: random + session ID + keyshare + memmove(msg_server_hello + 6, tls->random, sizeof(tls->random)); + memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id)); + memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub)); + + // server hello message + mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5); + mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello)); + mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello)); + + // change cipher message + mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6); +} + +static void mg_tls_server_send_ext(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; // server extensions uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0}; mg_sha256_update(&tls->sha256, ext, sizeof(ext)); - mg_tls_encrypt(c, ext, sizeof(ext), 0x16); + mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE); } -static void mg_tls_server_cert(struct mg_connection *c) { - struct tls_data *tls = c->tls; +static void mg_tls_server_send_cert(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; // server DER certificate (empty) size_t n = tls->server_cert_der.len; - uint8_t *cert = calloc(1, 13 + n); // FIXME: free + uint8_t *cert = (uint8_t *) calloc(1, 13 + n); + if (cert == NULL) { + mg_error(c, "tls cert oom"); + return; + } cert[0] = 0x0b; // handshake header cert[1] = (uint8_t) (((n + 9) >> 16) & 255U); // 3 bytes: payload length cert[2] = (uint8_t) (((n + 9) >> 8) & 255U); @@ -9738,35 +9943,37 @@ static void mg_tls_server_cert(struct mg_connection *c) { cert[9] = (uint8_t) (((n) >> 8) & 255U); cert[10] = (uint8_t) (n & 255U); // bytes 11+ are certificate in DER format - memmove(cert + 11, tls->server_cert_der.ptr, n); + memmove(cert + 11, tls->server_cert_der.buf, n); cert[11 + n] = cert[12 + n] = 0; // certificate extensions (none) mg_sha256_update(&tls->sha256, cert, 13 + n); - mg_tls_encrypt(c, cert, 13 + n, 0x16); + mg_tls_encrypt(c, cert, 13 + n, MG_TLS_HANDSHAKE); + free(cert); } // type adapter between uECC hash context and our sha256 implementation typedef struct SHA256_HashContext { - uECC_HashContext uECC; + MG_UECC_HashContext uECC; mg_sha256_ctx ctx; } SHA256_HashContext; -static void init_SHA256(const uECC_HashContext *base) { +static void init_SHA256(const MG_UECC_HashContext *base) { SHA256_HashContext *c = (SHA256_HashContext *) base; mg_sha256_init(&c->ctx); } -static void update_SHA256(const uECC_HashContext *base, const uint8_t *message, - unsigned message_size) { +static void update_SHA256(const MG_UECC_HashContext *base, + const uint8_t *message, unsigned message_size) { SHA256_HashContext *c = (SHA256_HashContext *) base; mg_sha256_update(&c->ctx, message, message_size); } -static void finish_SHA256(const uECC_HashContext *base, uint8_t *hash_result) { +static void finish_SHA256(const MG_UECC_HashContext *base, + uint8_t *hash_result) { SHA256_HashContext *c = (SHA256_HashContext *) base; mg_sha256_final(hash_result, &c->ctx); } -static void mg_tls_server_verify_ecdsa(struct mg_connection *c) { - struct tls_data *tls = c->tls; +static void mg_tls_server_send_cert_verify(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; // server certificate verify packet uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00}; size_t sigsz, verifysz = 0; @@ -9775,20 +9982,12 @@ static void mg_tls_server_verify_ecdsa(struct mg_connection *c) { {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp}, {{0}, 0, 0, {0}}}; int neg1, neg2; - uint8_t sig[64], sig_content[130] = { - " " - " " - "TLS 1.3, server CertificateVerify\0"}; - mg_sha256_ctx sha256; - memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); - mg_sha256_final(sig_content + 98, &sha256); + uint8_t sig[64] = {0}; - mg_sha256_init(&sha256); - mg_sha256_update(&sha256, sig_content, sizeof(sig_content)); - mg_sha256_final(hash, &sha256); + mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash); - uECC_sign_deterministic(tls->server_key, hash, sizeof(hash), &ctx.uECC, sig, - uECC_secp256r1()); + mg_uecc_sign_deterministic(tls->server_key, hash, sizeof(hash), &ctx.uECC, + sig, mg_uecc_secp256r1()); neg1 = !!(sig[0] & 0x80); neg2 = !!(sig[32] & 0x80); @@ -9806,14 +10005,12 @@ static void mg_tls_server_verify_ecdsa(struct mg_connection *c) { verify[3] = (uint8_t) (sigsz + 4); verify[7] = (uint8_t) sigsz; - mg_tls_hexdump("verify", verify, verifysz); - mg_sha256_update(&tls->sha256, verify, verifysz); - mg_tls_encrypt(c, verify, verifysz, 0x16); + mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE); } -static void mg_tls_server_finish(struct mg_connection *c) { - struct tls_data *tls = c->tls; +static void mg_tls_server_send_finish(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; struct mg_iobuf *wio = &tls->send; mg_sha256_ctx sha256; uint8_t hash[32]; @@ -9821,112 +10018,528 @@ static void mg_tls_server_finish(struct mg_connection *c) { memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); mg_sha256_final(hash, &sha256); mg_hmac_sha256(finish + 4, tls->server_finished_key, 32, hash, 32); - mg_tls_hexdump("hash", hash, sizeof(hash)); - mg_tls_hexdump("key", tls->server_finished_key, - sizeof(tls->server_finished_key)); - mg_tls_encrypt(c, finish, sizeof(finish), 0x16); + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE); mg_io_send(c, wio->buf, wio->len); wio->len = 0; mg_sha256_update(&tls->sha256, finish, sizeof(finish)); } -static int mg_tls_client_change_cipher(struct mg_connection *c) { - // struct tls_data *tls = c->tls; +static int mg_tls_server_recv_finish(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + // we have to backup sha256 value to restore it later, since Finished record + // is exceptional and is not supposed to be added to the rolling hash + // calculation. + mg_sha256_ctx sha256 = tls->sha256; + if (mg_tls_recv_record(c) < 0) { + return -1; + } + if (tls->recv.buf[0] != MG_TLS_FINISHED) { + mg_error(c, "expected Finish but got msg 0x%02x", tls->recv.buf[0]); + return -1; + } + mg_tls_drop_message(c); + + // restore hash + tls->sha256 = sha256; + return 0; +} + +static void mg_tls_client_send_hello(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + struct mg_iobuf *wio = &tls->send; + + const char *hostname = tls->hostname; + size_t hostnamesz = strlen(tls->hostname); + uint8_t x25519_pub[X25519_BYTES]; + + uint8_t msg_client_hello[162 + 32] = { + // TLS Client Hello header reported as TLS1.2 (5) + 0x16, + 0x03, + 0x01, + 0x00, + 0xfe, + // server hello, tls 1.2 (6) + 0x01, + 0x00, + 0x00, + 0x8c, + 0x03, + 0x03, + // random (32 bytes) + PLACEHOLDER_32B, + // session ID length + session ID (32 bytes) + 0x20, + PLACEHOLDER_32B, +#if defined(CHACHA20) && CHACHA20 + // TLS_CHACHA20_POLY1305_SHA256 + no compression + 0x13, + 0x03, + 0x00, +#else + 0x00, + 0x02, // size = 2 bytes + 0x13, + 0x01, // TLS_AES_128_GCM_SHA256 + 0x01, + 0x00, // no compression +#endif + + // extensions + keyshare + 0x00, + 0xfe, + // x25519 keyshare + 0x00, + 0x33, + 0x00, + 0x26, + 0x00, + 0x24, + 0x00, + 0x1d, + 0x00, + 0x20, + PLACEHOLDER_32B, + // supported groups (x25519) + 0x00, + 0x0a, + 0x00, + 0x04, + 0x00, + 0x02, + 0x00, + 0x1d, + // supported versions (tls1.3 == 0x304) + 0x00, + 0x2b, + 0x00, + 0x03, + 0x02, + 0x03, + 0x04, + // session ticket (none) + 0x00, + 0x23, + 0x00, + 0x00, + // signature algorithms (we don't care, so list all the common ones) + 0x00, + 0x0d, + 0x00, + 0x24, + 0x00, + 0x22, + 0x04, + 0x03, + 0x05, + 0x03, + 0x06, + 0x03, + 0x08, + 0x07, + 0x08, + 0x08, + 0x08, + 0x1a, + 0x08, + 0x1b, + 0x08, + 0x1c, + 0x08, + 0x09, + 0x08, + 0x0a, + 0x08, + 0x0b, + 0x08, + 0x04, + 0x08, + 0x05, + 0x08, + 0x06, + 0x04, + 0x01, + 0x05, + 0x01, + 0x06, + 0x01, + // server name + 0x00, + 0x00, + 0x00, + 0xfe, + 0x00, + 0xfe, + 0x00, + 0x00, + 0xfe + }; + + // patch ClientHello with correct hostname length + offset: + MG_STORE_BE16(msg_client_hello + 3, hostnamesz + 189); + MG_STORE_BE16(msg_client_hello + 7, hostnamesz + 185); + MG_STORE_BE16(msg_client_hello + 82, hostnamesz + 110); + MG_STORE_BE16(msg_client_hello + 187, hostnamesz + 5); + MG_STORE_BE16(msg_client_hello + 189, hostnamesz + 3); + MG_STORE_BE16(msg_client_hello + 192, hostnamesz); + + // calculate keyshare + mg_random(tls->x25519_cli, sizeof(tls->x25519_cli)); + mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1); + + // fill in the gaps: random + session ID + keyshare + mg_random(tls->session_id, sizeof(tls->session_id)); + mg_random(tls->random, sizeof(tls->random)); + memmove(msg_client_hello + 11, tls->random, sizeof(tls->random)); + memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id)); + memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub)); + + // server hello message + mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)); + mg_iobuf_add(wio, wio->len, hostname, strlen(hostname)); + mg_sha256_update(&tls->sha256, msg_client_hello + 5, + sizeof(msg_client_hello) - 5); + mg_sha256_update(&tls->sha256, (uint8_t *) hostname, strlen(hostname)); + + // change cipher message + mg_iobuf_add(wio, wio->len, (const char *) "\x14\x03\x03\x00\x01\x01", 6); + mg_io_send(c, wio->buf, wio->len); + wio->len = 0; +} + +static int mg_tls_client_recv_hello(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; struct mg_iobuf *rio = &c->rtls; - for (;;) { - if (!mg_tls_got_msg(c)) { - return MG_IO_WAIT; + uint16_t msgsz; + uint8_t *ext; + uint16_t ext_len; + int j; + + if (!mg_tls_got_record(c)) { + return MG_IO_WAIT; + } + if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_SERVER_HELLO) { + if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) { + mg_error(c, "tls alert %d", rio->buf[6]); + return -1; } - if (rio->buf[0] == 0x14) { // got a ChangeCipher record - break; - } else if (rio->buf[0] == 0x15) { // skip Alert records - MG_DEBUG(("TLS ALERT packet received")); - mg_tls_drop_packet(rio); - } else { - mg_error(c, "unexpected packet"); - return MG_IO_ERR; + MG_INFO(("got packet type 0x%02x/0x%02x", rio->buf[0], rio->buf[5])); + mg_error(c, "not a server hello packet"); + return -1; + } + + msgsz = MG_LOAD_BE16(rio->buf + 3); + mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz); + + ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3); + ext = rio->buf + 5 + 39 + 32 + 3 + 2; + + for (j = 0; j < ext_len;) { + uint16_t ext_type = MG_LOAD_BE16(ext + j); + uint16_t ext_len2 = MG_LOAD_BE16(ext + j + 2); + uint16_t group; + uint8_t *key_exchange; + uint16_t key_exchange_len; + if (ext_type != 0x0033) { // not a key share extension, ignore + j += (uint16_t) (ext_len2 + 4); + continue; } + group = MG_LOAD_BE16(ext + j + 4); + if (group != 0x001d) { + mg_error(c, "bad key exchange group"); + return -1; + } + key_exchange_len = MG_LOAD_BE16(ext + j + 6); + key_exchange = ext + j + 8; + if (key_exchange_len != 32) { + mg_error(c, "bad key exchange length"); + return -1; + } + mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1); + mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32); + mg_tls_drop_record(c); + /* generate handshake keys */ + mg_tls_generate_handshake_keys(c); + return 0; + } + mg_error(c, "bad client hello"); + return -1; +} + +static int mg_tls_client_recv_ext(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + if (mg_tls_recv_record(c) < 0) { + return -1; + } + if (tls->recv.buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) { + mg_error(c, "expected server extensions but got msg 0x%02x", + tls->recv.buf[0]); + return -1; } - // consume ChangeCipher packet - mg_tls_drop_packet(rio); + mg_tls_drop_message(c); return 0; } -static int mg_tls_client_finish(struct mg_connection *c) { - uint8_t tmp[2048]; - int n = mg_tls_recv_decrypt(c, tmp, sizeof(tmp)); - if (n < 0) { +static int mg_tls_client_recv_cert(struct mg_connection *c) { + uint8_t *cert; + uint32_t certsz; + struct mg_der_tlv oid, pubkey, seq, subj; + int subj_match = 0; + struct tls_data *tls = (struct tls_data *) c->tls; + if (mg_tls_recv_record(c) < 0) { + return -1; + } + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE) { + mg_error(c, "expected server certificate but got msg 0x%02x", + tls->recv.buf[0]); + return -1; + } + if (tls->skip_verification) { + mg_tls_drop_message(c); + return 0; + } + + if (tls->recv.len < 11) { + mg_error(c, "certificate list too short"); + return -1; + } + + cert = tls->recv.buf + 11; + certsz = MG_LOAD_BE24(tls->recv.buf + 8); + if (certsz > tls->recv.len - 11) { + mg_error(c, "certificate too long: %d vs %d", certsz, tls->recv.len - 11); return -1; } - // TODO: make sure it's a ClientFinish record + + do { + // secp256r1 public key + if (mg_der_find(cert, certsz, + (uint8_t *) "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8, + &oid) < 0) { + mg_error(c, "certificate secp256r1 public key OID not found"); + return -1; + } + if (mg_der_to_tlv(oid.value + oid.len, + (size_t) (cert + certsz - (oid.value + oid.len)), + &pubkey) < 0) { + mg_error(c, "certificate secp256r1 public key not found"); + return -1; + } + + // expect BIT STRING, unpadded, uncompressed: [0]+[4]+32+32 content bytes + if (pubkey.type != 3 || pubkey.len != 66 || pubkey.value[0] != 0 || + pubkey.value[1] != 4) { + mg_error(c, "unsupported public key bitstring encoding"); + return -1; + } + memmove(tls->pubkey, pubkey.value + 2, pubkey.len - 2); + } while (0); + + // Subject Alternative Names + do { + if (mg_der_find(cert, certsz, (uint8_t *) "\x55\x1d\x11", 3, &oid) < 0) { + mg_error(c, "certificate does not contain subject alternative names"); + return -1; + } + if (mg_der_to_tlv(oid.value + oid.len, + (size_t) (cert + certsz - (oid.value + oid.len)), + &seq) < 0) { + mg_error(c, "certificate subject alternative names not found"); + return -1; + } + if (mg_der_to_tlv(seq.value, seq.len, &seq) < 0) { + mg_error( + c, + "certificate subject alternative names is not a constructed object"); + return -1; + } + MG_VERBOSE(("verify hostname %s", tls->hostname)); + while (seq.len > 0) { + if (mg_der_to_tlv(seq.value, seq.len, &subj) < 0) { + mg_error(c, "bad subject alternative name"); + return -1; + } + MG_VERBOSE(("subj=%.*s", subj.len, subj.value)); + if (mg_match(mg_str((const char *) tls->hostname), + mg_str_n((const char *) subj.value, subj.len), NULL)) { + subj_match = 1; + break; + } + seq.len = (uint32_t) (seq.value + seq.len - (subj.value + subj.len)); + seq.value = subj.value + subj.len; + } + if (!subj_match) { + mg_error(c, "certificate did not match the hostname"); + return -1; + } + } while (0); + + mg_tls_drop_message(c); + mg_tls_calc_cert_verify_hash(c, tls->sighash); return 0; } -static void mg_tls_generate_application_keys(struct mg_connection *c) { - struct tls_data *tls = c->tls; - uint8_t hash[32]; - uint8_t premaster_secret[32]; - uint8_t master_secret[32]; - uint8_t server_secret[32]; - uint8_t client_secret[32]; +static int mg_tls_client_recv_cert_verify(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + if (mg_tls_recv_record(c) < 0) { + return -1; + } + if (tls->recv.buf[0] != MG_TLS_CERTIFICATE_VERIFY) { + mg_error(c, "expected server certificate verify but got msg 0x%02x", + tls->recv.buf[0]); + return -1; + } + // Ignore CertificateVerify is strict checks are not required + if (tls->skip_verification) { + mg_tls_drop_message(c); + return 0; + } + + // Extract certificate signature and verify it using pubkey and sighash + do { + uint8_t sig[64]; + struct mg_der_tlv seq, a, b; + if (mg_der_to_tlv(tls->recv.buf + 8, tls->recv.len - 8, &seq) < 0) { + mg_error(c, "verification message is not an ASN.1 DER sequence"); + return -1; + } + if (mg_der_to_tlv(seq.value, seq.len, &a) < 0) { + mg_error(c, "missing first part of the signature"); + return -1; + } + if (mg_der_to_tlv(a.value + a.len, seq.len - a.len, &b) < 0) { + mg_error(c, "missing second part of the signature"); + return -1; + } + // Integers may be padded with zeroes + if (a.len > 32) { + a.value = a.value + (a.len - 32); + a.len = 32; + } + if (b.len > 32) { + b.value = b.value + (b.len - 32); + b.len = 32; + } + + memmove(sig, a.value, a.len); + memmove(sig + 32, b.value, b.len); + if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig, + mg_uecc_secp256r1()) != 1) { + mg_error(c, "failed to verify certificate"); + return -1; + } + } while (0); + + mg_tls_drop_message(c); + return 0; +} + +static int mg_tls_client_recv_finish(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + if (mg_tls_recv_record(c) < 0) { + return -1; + } + if (tls->recv.buf[0] != MG_TLS_FINISHED) { + mg_error(c, "expected server finished but got msg 0x%02x", + tls->recv.buf[0]); + return -1; + } + mg_tls_drop_message(c); + return 0; +} + +static void mg_tls_client_send_finish(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + struct mg_iobuf *wio = &tls->send; mg_sha256_ctx sha256; + uint8_t hash[32]; + uint8_t finish[36] = {0x14, 0, 0, 32}; memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx)); mg_sha256_final(hash, &sha256); + mg_hmac_sha256(finish + 4, tls->client_finished_key, 32, hash, 32); + mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE); + mg_io_send(c, wio->buf, wio->len); + wio->len = 0; +} - mg_tls_derive_secret("tls13 derived", tls->handshake_secret, 32, - zeros_sha256_digest, 32, premaster_secret, 32); - mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32); - - mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32, - server_secret, 32); - mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0, - tls->server_write_key, 16); - mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0, - tls->server_write_iv, 12); - mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32, - client_secret, 32); - mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0, - tls->client_write_key, 16); - mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0, - tls->client_write_iv, 12); - - tls->sseq = tls->cseq = 0; +static void mg_tls_client_handshake(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; + switch (tls->state) { + case MG_TLS_STATE_CLIENT_START: + mg_tls_client_send_hello(c); + tls->state = MG_TLS_STATE_CLIENT_WAIT_SH; + // Fallthrough + case MG_TLS_STATE_CLIENT_WAIT_SH: + if (mg_tls_client_recv_hello(c) < 0) { + break; + } + tls->state = MG_TLS_STATE_CLIENT_WAIT_EE; + // Fallthrough + case MG_TLS_STATE_CLIENT_WAIT_EE: + if (mg_tls_client_recv_ext(c) < 0) { + break; + } + tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT; + // Fallthrough + case MG_TLS_STATE_CLIENT_WAIT_CERT: + if (mg_tls_client_recv_cert(c) < 0) { + break; + } + tls->state = MG_TLS_STATE_CLIENT_WAIT_CV; + // Fallthrough + case MG_TLS_STATE_CLIENT_WAIT_CV: + if (mg_tls_client_recv_cert_verify(c) < 0) { + break; + } + tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISHED; + // Fallthrough + case MG_TLS_STATE_CLIENT_WAIT_FINISHED: + if (mg_tls_client_recv_finish(c) < 0) { + break; + } + mg_tls_client_send_finish(c); + mg_tls_generate_application_keys(c); + tls->state = MG_TLS_STATE_CLIENT_CONNECTED; + c->is_tls_hs = 0; + break; + default: mg_error(c, "unexpected client state: %d", tls->state); break; + } } -void mg_tls_handshake(struct mg_connection *c) { - struct tls_data *tls = c->tls; +static void mg_tls_server_handshake(struct mg_connection *c) { + struct tls_data *tls = (struct tls_data *) c->tls; switch (tls->state) { - case MG_TLS_HS_CLIENT_HELLO: - if (mg_tls_client_hello(c) < 0) { + case MG_TLS_STATE_SERVER_START: + if (mg_tls_server_recv_hello(c) < 0) { return; } - tls->state = MG_TLS_HS_SERVER_HELLO; - // fallthrough - case MG_TLS_HS_SERVER_HELLO: - mg_tls_server_hello(c); + mg_tls_server_send_hello(c); mg_tls_generate_handshake_keys(c); - mg_tls_server_extensions(c); - mg_tls_server_cert(c); - mg_tls_server_verify_ecdsa(c); - mg_tls_server_finish(c); - tls->state = MG_TLS_HS_CLIENT_CHANGE_CIPHER; - // fallthrough - case MG_TLS_HS_CLIENT_CHANGE_CIPHER: - if (mg_tls_client_change_cipher(c) < 0) { - return; - } - tls->state = MG_TLS_HS_CLIENT_FINISH; + mg_tls_server_send_ext(c); + mg_tls_server_send_cert(c); + mg_tls_server_send_cert_verify(c); + mg_tls_server_send_finish(c); + tls->state = MG_TLS_STATE_SERVER_NEGOTIATED; // fallthrough - case MG_TLS_HS_CLIENT_FINISH: - if (mg_tls_client_finish(c) < 0) { + case MG_TLS_STATE_SERVER_NEGOTIATED: + if (mg_tls_server_recv_finish(c) < 0) { return; } mg_tls_generate_application_keys(c); - tls->state = MG_TLS_HS_DONE; - // fallthrough - case MG_TLS_HS_DONE: c->is_tls_hs = 0; return; + tls->state = MG_TLS_STATE_SERVER_CONNECTED; + c->is_tls_hs = 0; + return; + default: mg_error(c, "unexpected server state: %d", tls->state); break; + } +} + +void mg_tls_handshake(struct mg_connection *c) { + if (c->is_client) { + mg_tls_client_handshake(c); + } else { + mg_tls_server_handshake(c); } } @@ -9943,11 +10556,11 @@ static int mg_parse_pem(const struct mg_str pem, const struct mg_str label, if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label) != 0) { return -1; // bad label } - if ((s = calloc(1, caps[2].len)) == NULL) { + if ((s = (char *) calloc(1, caps[2].len)) == NULL) { return -1; } - for (c = caps[2].ptr; c < caps[2].ptr + caps[2].len; c++) { + for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) { if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') { continue; } @@ -9958,7 +10571,7 @@ static int mg_parse_pem(const struct mg_str pem, const struct mg_str label, free(s); return -1; } - der->ptr = s; + der->buf = s; der->len = m; return 0; } @@ -9970,27 +10583,30 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { mg_error(c, "tls oom"); return; } - // parse PEM or DER EC key - if (opts->key.ptr == NULL || - mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) < 0) { - MG_ERROR(("Failed to load EC private key")); - return; - } - if (key.len < 39) { - MG_ERROR(("EC private key too short")); - return; + + tls->state = + c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START; + + tls->skip_verification = opts->skip_verification; + tls->send.align = MG_IO_SIZE; + + c->tls = tls; + c->is_tls = c->is_tls_hs = 1; + mg_sha256_init(&tls->sha256); + + // save hostname (client extension) + if (opts->name.len > 0) { + if (opts->name.len >= sizeof(tls->hostname) - 1) { + mg_error(c, "hostname too long"); + } + strncpy((char *) tls->hostname, opts->name.buf, sizeof(tls->hostname) - 1); + tls->hostname[opts->name.len] = 0; } - // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...] - // 30 nn 02 01 01 04 20 [key] ... - if (key.ptr[0] != 0x30 || (key.ptr[1] & 0x80) != 0) { - MG_ERROR(("EC private key: ASN.1 bad sequence")); + + if (c->is_client) { + tls->server_cert_der.buf = NULL; return; } - if (memcmp(key.ptr + 2, "\x02\x01\x01\x04\x20", 5) != 0) { - MG_ERROR(("EC private key: ASN.1 bad data")); - } - memmove(tls->server_key, key.ptr + 7, 32); - free((void *) key.ptr); // parse PEM or DER certificate if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"), &tls->server_cert_der) < @@ -9999,28 +10615,50 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { return; } - // tls->send.align = tls->recv.align = MG_IO_SIZE; - tls->send.align = MG_IO_SIZE; - c->tls = tls; - c->is_tls = c->is_tls_hs = 1; - mg_sha256_init(&tls->sha256); + // parse PEM or DER EC key + if (opts->key.buf == NULL) { + mg_error(c, "certificate provided without a private key"); + return; + } + + if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) == 0) { + if (key.len < 39) { + MG_ERROR(("EC private key too short")); + return; + } + // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...] + // 30 nn 02 01 01 04 20 [key] ... + if (key.buf[0] != 0x30 || (key.buf[1] & 0x80) != 0) { + MG_ERROR(("EC private key: ASN.1 bad sequence")); + return; + } + if (memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) { + MG_ERROR(("EC private key: ASN.1 bad data")); + } + memmove(tls->server_key, key.buf + 7, 32); + free((void *) key.buf); + } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) { + mg_error(c, "PKCS8 private key format is not supported"); + } else { + mg_error(c, "expected EC PRIVATE KEY or PRIVATE KEY"); + } } void mg_tls_free(struct mg_connection *c) { - struct tls_data *tls = c->tls; + struct tls_data *tls = (struct tls_data *) c->tls; if (tls != NULL) { mg_iobuf_free(&tls->send); - free((void *) tls->server_cert_der.ptr); + free((void *) tls->server_cert_der.buf); } free(c->tls); c->tls = NULL; } long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) { - struct tls_data *tls = c->tls; + struct tls_data *tls = (struct tls_data *) c->tls; long n = MG_IO_WAIT; if (len > MG_IO_SIZE) len = MG_IO_SIZE; - mg_tls_encrypt(c, buf, len, 0x17); + mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA); while (tls->send.len > 0 && (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) { mg_iobuf_del(&tls->send, 0, (size_t) n); @@ -10030,11 +10668,31 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) { } long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) { - return mg_tls_recv_decrypt(c, buf, len); + int r = 0; + struct tls_data *tls = (struct tls_data *) c->tls; + size_t minlen; + + r = mg_tls_recv_record(c); + if (r < 0) { + return r; + } + if (tls->content_type != MG_TLS_APP_DATA) { + tls->recv.len = 0; + mg_tls_drop_record(c); + return MG_IO_WAIT; + } + minlen = len < tls->recv.len ? len : tls->recv.len; + memmove(buf, tls->recv.buf, minlen); + tls->recv.buf += minlen; + tls->recv.len -= minlen; + if (tls->recv.len == 0) { + mg_tls_drop_record(c); + } + return (long) minlen; } size_t mg_tls_pending(struct mg_connection *c) { - return mg_tls_got_msg(c) ? 1 : 0; + return mg_tls_got_record(c) ? 1 : 0; } void mg_tls_ctx_init(struct mg_mgr *mgr) { @@ -10102,9 +10760,9 @@ static int mg_mbed_rng(void *ctx, unsigned char *buf, size_t len) { static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) { int rc; - if (str.ptr == NULL || str.ptr[0] == '\0' || str.ptr[0] == '*') return true; - if (str.ptr[0] == '-') str.len++; // PEM, include trailing NUL - if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.ptr, str.len)) != 0) { + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true; + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL + if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.buf, str.len)) != 0) { MG_ERROR(("cert err %#x", -rc)); return false; } @@ -10113,9 +10771,9 @@ static bool mg_load_cert(struct mg_str str, mbedtls_x509_crt *p) { static bool mg_load_key(struct mg_str str, mbedtls_pk_context *p) { int rc; - if (str.ptr == NULL || str.ptr[0] == '\0' || str.ptr[0] == '*') return true; - if (str.ptr[0] == '-') str.len++; // PEM, include trailing NUL - if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.ptr, str.len, NULL, + if (str.buf == NULL || str.buf[0] == '\0' || str.buf[0] == '*') return true; + if (str.buf[0] == '-') str.len++; // PEM, include trailing NUL + if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.buf, str.len, NULL, 0 MG_MBEDTLS_RNG_GET)) != 0) { MG_ERROR(("key err %#x", -rc)); return false; @@ -10213,8 +10871,8 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { } else { if (mg_load_cert(opts->ca, &tls->ca) == false) goto fail; mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL); - if (c->is_client && opts->name.ptr != NULL && opts->name.ptr[0] != '\0') { - char *host = mg_mprintf("%.*s", opts->name.len, opts->name.ptr); + if (c->is_client && opts->name.buf != NULL && opts->name.buf[0] != '\0') { + char *host = mg_mprintf("%.*s", opts->name.len, opts->name.buf); mbedtls_ssl_set_hostname(&tls->ssl, host); MG_DEBUG(("%lu hostname verification: %s", c->id, host)); free(host); @@ -10336,7 +10994,7 @@ static int mg_tls_err(struct mg_connection *c, struct mg_tls *tls, int res) { } static STACK_OF(X509_INFO) * load_ca_certs(struct mg_str ca) { - BIO *bio = BIO_new_mem_buf(ca.ptr, (int) ca.len); + BIO *bio = BIO_new_mem_buf(ca.buf, (int) ca.len); STACK_OF(X509_INFO) *certs = bio ? PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL) : NULL; if (bio) BIO_free(bio); @@ -10354,16 +11012,16 @@ static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) * certs) { } static EVP_PKEY *load_key(struct mg_str s) { - BIO *bio = BIO_new_mem_buf(s.ptr, (int) (long) s.len); + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len); EVP_PKEY *key = bio ? PEM_read_bio_PrivateKey(bio, NULL, 0, NULL) : NULL; if (bio) BIO_free(bio); return key; } static X509 *load_cert(struct mg_str s) { - BIO *bio = BIO_new_mem_buf(s.ptr, (int) (long) s.len); + BIO *bio = BIO_new_mem_buf(s.buf, (int) (long) s.len); X509 *cert = bio == NULL ? NULL - : s.ptr[0] == '-' + : s.buf[0] == '-' ? PEM_read_bio_X509(bio, NULL, NULL, NULL) // PEM : d2i_X509_bio(bio, NULL); // DER if (bio) BIO_free(bio); @@ -10437,7 +11095,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE); #endif - if (opts->ca.ptr != NULL && opts->ca.ptr[0] != '\0') { + if (opts->ca.buf != NULL && opts->ca.buf[0] != '\0') { SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); STACK_OF(X509_INFO) *certs = load_ca_certs(opts->ca); @@ -10448,7 +11106,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { goto fail; } } - if (opts->cert.ptr != NULL && opts->cert.ptr[0] != '\0') { + if (opts->cert.buf != NULL && opts->cert.buf[0] != '\0') { X509 *cert = load_cert(opts->cert); rc = cert == NULL ? 0 : SSL_use_certificate(tls->ssl, cert); X509_free(cert); @@ -10457,7 +11115,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { goto fail; } } - if (opts->key.ptr != NULL && opts->key.ptr[0] != '\0') { + if (opts->key.buf != NULL && opts->key.buf[0] != '\0') { EVP_PKEY *key = load_key(opts->key); rc = key == NULL ? 0 : SSL_use_PrivateKey(tls->ssl, key); EVP_PKEY_free(key); @@ -10473,7 +11131,7 @@ void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) { #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (opts->name.len > 0) { - char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.ptr); + char *s = mg_mprintf("%.*s", (int) opts->name.len, opts->name.buf); SSL_set1_host(tls->ssl, s); SSL_set_tlsext_host_name(tls->ssl, s); free(s); @@ -10564,18 +11222,19 @@ void mg_tls_ctx_free(struct mg_mgr *mgr) { #if MG_TLS == MG_TLS_BUILTIN -#ifndef uECC_RNG_MAX_TRIES -#define uECC_RNG_MAX_TRIES 64 +#ifndef MG_UECC_RNG_MAX_TRIES +#define MG_UECC_RNG_MAX_TRIES 64 #endif -#if uECC_ENABLE_VLI_API -#define uECC_VLI_API +#if MG_UECC_ENABLE_VLI_API +#define MG_UECC_VLI_API #else -#define uECC_VLI_API static +#define MG_UECC_VLI_API static #endif -#if (uECC_PLATFORM == uECC_avr) || (uECC_PLATFORM == uECC_arm) || \ - (uECC_PLATFORM == uECC_arm_thumb) || (uECC_PLATFORM == uECC_arm_thumb2) +#if (MG_UECC_PLATFORM == mg_uecc_avr) || (MG_UECC_PLATFORM == mg_uecc_arm) || \ + (MG_UECC_PLATFORM == mg_uecc_arm_thumb) || \ + (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) #define CONCATX(a, ...) a##__VA_ARGS__ #define CONCAT(a, ...) CONCATX(a, __VA_ARGS__) @@ -10646,83 +11305,84 @@ void mg_tls_ctx_free(struct mg_mgr *mgr) { #define REPEATM(N, macro) EVAL(REPEATM_SOME(N, macro)) #endif -// +// -#if (uECC_WORD_SIZE == 1) -#if uECC_SUPPORTS_secp160r1 -#define uECC_MAX_WORDS 21 /* Due to the size of curve_n. */ +#if (MG_UECC_WORD_SIZE == 1) +#if MG_UECC_SUPPORTS_secp160r1 +#define MG_UECC_MAX_WORDS 21 /* Due to the size of curve_n. */ #endif -#if uECC_SUPPORTS_secp192r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 24 +#if MG_UECC_SUPPORTS_secp192r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 24 #endif -#if uECC_SUPPORTS_secp224r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 28 +#if MG_UECC_SUPPORTS_secp224r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 28 #endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 32 +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1) +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 32 #endif -#elif (uECC_WORD_SIZE == 4) -#if uECC_SUPPORTS_secp160r1 -#define uECC_MAX_WORDS 6 /* Due to the size of curve_n. */ +#elif (MG_UECC_WORD_SIZE == 4) +#if MG_UECC_SUPPORTS_secp160r1 +#define MG_UECC_MAX_WORDS 6 /* Due to the size of curve_n. */ #endif -#if uECC_SUPPORTS_secp192r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 6 +#if MG_UECC_SUPPORTS_secp192r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 6 #endif -#if uECC_SUPPORTS_secp224r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 7 +#if MG_UECC_SUPPORTS_secp224r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 7 #endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 8 +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1) +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 8 #endif -#elif (uECC_WORD_SIZE == 8) -#if uECC_SUPPORTS_secp160r1 -#define uECC_MAX_WORDS 3 +#elif (MG_UECC_WORD_SIZE == 8) +#if MG_UECC_SUPPORTS_secp160r1 +#define MG_UECC_MAX_WORDS 3 #endif -#if uECC_SUPPORTS_secp192r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 3 +#if MG_UECC_SUPPORTS_secp192r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 3 #endif -#if uECC_SUPPORTS_secp224r1 -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 4 +#if MG_UECC_SUPPORTS_secp224r1 +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 4 #endif -#if (uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1) -#undef uECC_MAX_WORDS -#define uECC_MAX_WORDS 4 +#if (MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1) +#undef MG_UECC_MAX_WORDS +#define MG_UECC_MAX_WORDS 4 #endif -#endif /* uECC_WORD_SIZE */ +#endif /* MG_UECC_WORD_SIZE */ -#define BITS_TO_WORDS(num_bits) \ - ((wordcount_t) ((num_bits + ((uECC_WORD_SIZE * 8) - 1)) / \ - (uECC_WORD_SIZE * 8))) +#define BITS_TO_WORDS(num_bits) \ + ((wordcount_t) ((num_bits + ((MG_UECC_WORD_SIZE * 8) - 1)) / \ + (MG_UECC_WORD_SIZE * 8))) #define BITS_TO_BYTES(num_bits) ((num_bits + 7) / 8) -struct uECC_Curve_t { +struct MG_UECC_Curve_t { wordcount_t num_words; wordcount_t num_bytes; bitcount_t num_n_bits; - uECC_word_t p[uECC_MAX_WORDS]; - uECC_word_t n[uECC_MAX_WORDS]; - uECC_word_t G[uECC_MAX_WORDS * 2]; - uECC_word_t b[uECC_MAX_WORDS]; - void (*double_jacobian)(uECC_word_t *X1, uECC_word_t *Y1, uECC_word_t *Z1, - uECC_Curve curve); -#if uECC_SUPPORT_COMPRESSED_POINT - void (*mod_sqrt)(uECC_word_t *a, uECC_Curve curve); -#endif - void (*x_side)(uECC_word_t *result, const uECC_word_t *x, uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) - void (*mmod_fast)(uECC_word_t *result, uECC_word_t *product); + mg_uecc_word_t p[MG_UECC_MAX_WORDS]; + mg_uecc_word_t n[MG_UECC_MAX_WORDS]; + mg_uecc_word_t G[MG_UECC_MAX_WORDS * 2]; + mg_uecc_word_t b[MG_UECC_MAX_WORDS]; + void (*double_jacobian)(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *Z1, MG_UECC_Curve curve); +#if MG_UECC_SUPPORT_COMPRESSED_POINT + void (*mod_sqrt)(mg_uecc_word_t *a, MG_UECC_Curve curve); +#endif + void (*x_side)(mg_uecc_word_t *result, const mg_uecc_word_t *x, + MG_UECC_Curve curve); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) + void (*mmod_fast)(mg_uecc_word_t *result, mg_uecc_word_t *product); #endif }; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN static void bcopy(uint8_t *dst, const uint8_t *src, unsigned num_bytes) { while (0 != num_bytes) { num_bytes--; @@ -10731,16 +11391,17 @@ static void bcopy(uint8_t *dst, const uint8_t *src, unsigned num_bytes) { } #endif -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); -#if (uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) +#if (MG_UECC_PLATFORM == mg_uecc_arm || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) #endif -#if (uECC_PLATFORM == uECC_avr) +#if (MG_UECC_PLATFORM == mg_uecc_avr) #endif @@ -10767,29 +11428,30 @@ static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, #endif #if defined(default_RNG_defined) && default_RNG_defined -static uECC_RNG_Function g_rng_function = &default_RNG; +static MG_UECC_RNG_Function g_rng_function = &default_RNG; #else -static uECC_RNG_Function g_rng_function = 0; +static MG_UECC_RNG_Function g_rng_function = 0; #endif -void uECC_set_rng(uECC_RNG_Function rng_function) { +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function) { g_rng_function = rng_function; } -uECC_RNG_Function uECC_get_rng(void) { +MG_UECC_RNG_Function mg_uecc_get_rng(void) { return g_rng_function; } -int uECC_curve_private_key_size(uECC_Curve curve) { +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve) { return BITS_TO_BYTES(curve->num_n_bits); } -int uECC_curve_public_key_size(uECC_Curve curve) { +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve) { return 2 * curve->num_bytes; } #if !asm_clear -uECC_VLI_API void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words) { +MG_UECC_VLI_API void mg_uecc_vli_clear(mg_uecc_word_t *vli, + wordcount_t num_words) { wordcount_t i; for (i = 0; i < num_words; ++i) { vli[i] = 0; @@ -10799,9 +11461,9 @@ uECC_VLI_API void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words) { /* Constant-time comparison to zero - secure way to compare long integers */ /* Returns 1 if vli == 0, 0 otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, - wordcount_t num_words) { - uECC_word_t bits = 0; +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli, + wordcount_t num_words) { + mg_uecc_word_t bits = 0; wordcount_t i; for (i = 0; i < num_words; ++i) { bits |= vli[i]; @@ -10810,14 +11472,14 @@ uECC_VLI_API uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, } /* Returns nonzero if bit 'bit' of vli is set. */ -uECC_VLI_API uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, - bitcount_t bit) { - return (vli[bit >> uECC_WORD_BITS_SHIFT] & - ((uECC_word_t) 1 << (bit & uECC_WORD_BITS_MASK))); +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, + bitcount_t bit) { + return (vli[bit >> MG_UECC_WORD_BITS_SHIFT] & + ((mg_uecc_word_t) 1 << (bit & MG_UECC_WORD_BITS_MASK))); } /* Counts the number of words in vli. */ -static wordcount_t vli_numDigits(const uECC_word_t *vli, +static wordcount_t vli_numDigits(const mg_uecc_word_t *vli, const wordcount_t max_words) { wordcount_t i; /* Search from the end until we find a non-zero digit. @@ -10829,10 +11491,10 @@ static wordcount_t vli_numDigits(const uECC_word_t *vli, } /* Counts the number of bits required to represent vli. */ -uECC_VLI_API bitcount_t uECC_vli_numBits(const uECC_word_t *vli, - const wordcount_t max_words) { - uECC_word_t i; - uECC_word_t digit; +MG_UECC_VLI_API bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli, + const wordcount_t max_words) { + mg_uecc_word_t i; + mg_uecc_word_t digit; wordcount_t num_digits = vli_numDigits(vli, max_words); if (num_digits == 0) { @@ -10844,14 +11506,15 @@ uECC_VLI_API bitcount_t uECC_vli_numBits(const uECC_word_t *vli, digit >>= 1; } - return (((bitcount_t) ((num_digits - 1) << uECC_WORD_BITS_SHIFT)) + + return (((bitcount_t) ((num_digits - 1) << MG_UECC_WORD_BITS_SHIFT)) + (bitcount_t) i); } /* Sets dest = src. */ #if !asm_set -uECC_VLI_API void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, - wordcount_t num_words) { +MG_UECC_VLI_API void mg_uecc_vli_set(mg_uecc_word_t *dest, + const mg_uecc_word_t *src, + wordcount_t num_words) { wordcount_t i; for (i = 0; i < num_words; ++i) { dest[i] = src[i]; @@ -10860,9 +11523,9 @@ uECC_VLI_API void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, #endif /* !asm_set */ /* Returns sign of left - right. */ -static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { +static cmpresult_t mg_uecc_vli_cmp_unsafe(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { wordcount_t i; for (i = num_words - 1; i >= 0; --i) { if (left[i] > right[i]) { @@ -10876,10 +11539,10 @@ static cmpresult_t uECC_vli_cmp_unsafe(const uECC_word_t *left, /* Constant-time comparison function - secure way to compare long integers */ /* Returns one if left == right, zero otherwise. */ -uECC_VLI_API uECC_word_t uECC_vli_equal(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t diff = 0; +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { + mg_uecc_word_t diff = 0; wordcount_t i; for (i = num_words - 1; i >= 0; --i) { diff |= (left[i] ^ right[i]); @@ -10887,46 +11550,47 @@ uECC_VLI_API uECC_word_t uECC_vli_equal(const uECC_word_t *left, return (diff == 0); } -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words); +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); /* Returns sign of left - right, in constant time. */ -uECC_VLI_API cmpresult_t uECC_vli_cmp(const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t neg = !!uECC_vli_sub(tmp, left, right, num_words); - uECC_word_t equal = uECC_vli_isZero(tmp, num_words); +MG_UECC_VLI_API cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS]; + mg_uecc_word_t neg = !!mg_uecc_vli_sub(tmp, left, right, num_words); + mg_uecc_word_t equal = mg_uecc_vli_isZero(tmp, num_words); return (cmpresult_t) (!equal - 2 * neg); } /* Computes vli = vli >> 1. */ #if !asm_rshift1 -uECC_VLI_API void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words) { - uECC_word_t *end = vli; - uECC_word_t carry = 0; +MG_UECC_VLI_API void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, + wordcount_t num_words) { + mg_uecc_word_t *end = vli; + mg_uecc_word_t carry = 0; vli += num_words; while (vli-- > end) { - uECC_word_t temp = *vli; + mg_uecc_word_t temp = *vli; *vli = (temp >> 1) | carry; - carry = temp << (uECC_WORD_BITS - 1); + carry = temp << (MG_UECC_WORD_BITS - 1); } } #endif /* !asm_rshift1 */ /* Computes result = left + right, returning carry. Can modify in place. */ #if !asm_add -uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t carry = 0; +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { + mg_uecc_word_t carry = 0; wordcount_t i; for (i = 0; i < num_words; ++i) { - uECC_word_t sum = left[i] + right[i] + carry; + mg_uecc_word_t sum = left[i] + right[i] + carry; if (sum != left[i]) { carry = (sum < left[i]); } @@ -10938,14 +11602,14 @@ uECC_VLI_API uECC_word_t uECC_vli_add(uECC_word_t *result, /* Computes result = left - right, returning borrow. Can modify in place. */ #if !asm_sub -uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t borrow = 0; +MG_UECC_VLI_API mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { + mg_uecc_word_t borrow = 0; wordcount_t i; for (i = 0; i < num_words; ++i) { - uECC_word_t diff = left[i] - right[i] - borrow; + mg_uecc_word_t diff = left[i] - right[i] - borrow; if (diff != left[i]) { borrow = (diff > left[i]); } @@ -10955,12 +11619,12 @@ uECC_VLI_API uECC_word_t uECC_vli_sub(uECC_word_t *result, } #endif /* !asm_sub */ -#if !asm_mult || (uECC_SQUARE_FUNC && !asm_square) || \ - (uECC_SUPPORTS_secp256k1 && (uECC_OPTIMIZATION_LEVEL > 0) && \ - ((uECC_WORD_SIZE == 1) || (uECC_WORD_SIZE == 8))) -static void muladd(uECC_word_t a, uECC_word_t b, uECC_word_t *r0, - uECC_word_t *r1, uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 +#if !asm_mult || (MG_UECC_SQUARE_FUNC && !asm_square) || \ + (MG_UECC_SUPPORTS_secp256k1 && (MG_UECC_OPTIMIZATION_LEVEL > 0) && \ + ((MG_UECC_WORD_SIZE == 1) || (MG_UECC_WORD_SIZE == 8))) +static void muladd(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0, + mg_uecc_word_t *r1, mg_uecc_word_t *r2) { +#if MG_UECC_WORD_SIZE == 8 uint64_t a0 = a & 0xffffffff; uint64_t a1 = a >> 32; uint64_t b0 = b & 0xffffffff; @@ -10986,23 +11650,24 @@ static void muladd(uECC_word_t a, uECC_word_t b, uECC_word_t *r0, *r1 += (p1 + (*r0 < p0)); *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); #else - uECC_dword_t p = (uECC_dword_t) a * b; - uECC_dword_t r01 = ((uECC_dword_t) (*r1) << uECC_WORD_BITS) | *r0; + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b; + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0; r01 += p; *r2 += (r01 < p); - *r1 = (uECC_word_t) (r01 >> uECC_WORD_BITS); - *r0 = (uECC_word_t) r01; + *r1 = (mg_uecc_word_t) (r01 >> MG_UECC_WORD_BITS); + *r0 = (mg_uecc_word_t) r01; #endif } #endif /* muladd needed */ #if !asm_mult -uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; +MG_UECC_VLI_API void mg_uecc_vli_mult(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words) { + mg_uecc_word_t r0 = 0; + mg_uecc_word_t r1 = 0; + mg_uecc_word_t r2 = 0; wordcount_t i, k; /* Compute each digit of result in sequence, maintaining the carries. */ @@ -11028,12 +11693,12 @@ uECC_VLI_API void uECC_vli_mult(uECC_word_t *result, const uECC_word_t *left, } #endif /* !asm_mult */ -#if uECC_SQUARE_FUNC +#if MG_UECC_SQUARE_FUNC #if !asm_square -static void mul2add(uECC_word_t a, uECC_word_t b, uECC_word_t *r0, - uECC_word_t *r1, uECC_word_t *r2) { -#if uECC_WORD_SIZE == 8 +static void mul2add(mg_uecc_word_t a, mg_uecc_word_t b, mg_uecc_word_t *r0, + mg_uecc_word_t *r1, mg_uecc_word_t *r2) { +#if MG_UECC_WORD_SIZE == 8 uint64_t a0 = a & 0xffffffffull; uint64_t a1 = a >> 32; uint64_t b0 = b & 0xffffffffull; @@ -11063,27 +11728,28 @@ static void mul2add(uECC_word_t a, uECC_word_t b, uECC_word_t *r0, *r1 += (p1 + (*r0 < p0)); *r2 += ((*r1 < p1) || (*r1 == p1 && *r0 < p0)); #else - uECC_dword_t p = (uECC_dword_t) a * b; - uECC_dword_t r01 = ((uECC_dword_t) (*r1) << uECC_WORD_BITS) | *r0; - *r2 += (p >> (uECC_WORD_BITS * 2 - 1)); + mg_uecc_dword_t p = (mg_uecc_dword_t) a * b; + mg_uecc_dword_t r01 = ((mg_uecc_dword_t) (*r1) << MG_UECC_WORD_BITS) | *r0; + *r2 += (p >> (MG_UECC_WORD_BITS * 2 - 1)); p *= 2; r01 += p; *r2 += (r01 < p); - *r1 = r01 >> uECC_WORD_BITS; - *r0 = (uECC_word_t) r01; + *r1 = r01 >> MG_UECC_WORD_BITS; + *r0 = (mg_uecc_word_t) r01; #endif } -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, - wordcount_t num_words) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + wordcount_t num_words) { + mg_uecc_word_t r0 = 0; + mg_uecc_word_t r1 = 0; + mg_uecc_word_t r2 = 0; wordcount_t i, k; for (k = 0; k < num_words * 2 - 1; ++k) { - uECC_word_t min = (k < num_words ? 0 : (k + 1) - num_words); + mg_uecc_word_t min = (k < num_words ? 0 : (k + 1) - num_words); for (i = min; i <= k && i <= k - i; ++i) { if (i < k - i) { mul2add(left[i], left[k - i], &r0, &r1, &r2); @@ -11101,168 +11767,174 @@ uECC_VLI_API void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, } #endif /* !asm_square */ -#else /* uECC_SQUARE_FUNC */ +#else /* MG_UECC_SQUARE_FUNC */ -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, - wordcount_t num_words) { - uECC_vli_mult(result, left, left, num_words); +#if MG_UECC_ENABLE_VLI_API +MG_UECC_VLI_API void mg_uecc_vli_square(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + wordcount_t num_words) { + mg_uecc_vli_mult(result, left, left, num_words); } -#endif /* uECC_ENABLE_VLI_API */ +#endif /* MG_UECC_ENABLE_VLI_API */ -#endif /* uECC_SQUARE_FUNC */ +#endif /* MG_UECC_SQUARE_FUNC */ /* Computes result = (left + right) % mod. Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modAdd(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t carry = uECC_vli_add(result, left, right, num_words); - if (carry || uECC_vli_cmp_unsafe(mod, result, num_words) != 1) { +MG_UECC_VLI_API void mg_uecc_vli_modAdd(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t carry = mg_uecc_vli_add(result, left, right, num_words); + if (carry || mg_uecc_vli_cmp_unsafe(mod, result, num_words) != 1) { /* result > mod (result = mod + remainder), so subtract mod to get * remainder. */ - uECC_vli_sub(result, result, mod, num_words); + mg_uecc_vli_sub(result, result, mod, num_words); } } /* Computes result = (left - right) % mod. Assumes that left < mod and right < mod, and that result does not overlap mod. */ -uECC_VLI_API void uECC_vli_modSub(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t l_borrow = uECC_vli_sub(result, left, right, num_words); +MG_UECC_VLI_API void mg_uecc_vli_modSub(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t l_borrow = mg_uecc_vli_sub(result, left, right, num_words); if (l_borrow) { /* In this case, result == -diff == (max int) - diff. Since -x % d == d - x, we can get the correct result from result + mod (with overflow). */ - uECC_vli_add(result, result, mod, num_words); + mg_uecc_vli_add(result, result, mod, num_words); } } /* Computes result = product % mod, where product is 2N words long. */ /* Currently only designed to work for curve_p or curve_n. */ -uECC_VLI_API void uECC_vli_mmod(uECC_word_t *result, uECC_word_t *product, - const uECC_word_t *mod, wordcount_t num_words) { - uECC_word_t mod_multiple[2 * uECC_MAX_WORDS]; - uECC_word_t tmp[2 * uECC_MAX_WORDS]; - uECC_word_t *v[2] = {tmp, product}; - uECC_word_t index; +MG_UECC_VLI_API void mg_uecc_vli_mmod(mg_uecc_word_t *result, + mg_uecc_word_t *product, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t mod_multiple[2 * MG_UECC_MAX_WORDS]; + mg_uecc_word_t tmp[2 * MG_UECC_MAX_WORDS]; + mg_uecc_word_t *v[2] = {tmp, product}; + mg_uecc_word_t index; /* Shift mod so its highest set bit is at the maximum position. */ - bitcount_t shift = (bitcount_t) ( - (num_words * 2 * uECC_WORD_BITS) - uECC_vli_numBits(mod, num_words)); - wordcount_t word_shift = (wordcount_t) (shift / uECC_WORD_BITS); - wordcount_t bit_shift = (wordcount_t) (shift % uECC_WORD_BITS); - uECC_word_t carry = 0; - uECC_vli_clear(mod_multiple, word_shift); + bitcount_t shift = (bitcount_t) ((num_words * 2 * MG_UECC_WORD_BITS) - + mg_uecc_vli_numBits(mod, num_words)); + wordcount_t word_shift = (wordcount_t) (shift / MG_UECC_WORD_BITS); + wordcount_t bit_shift = (wordcount_t) (shift % MG_UECC_WORD_BITS); + mg_uecc_word_t carry = 0; + mg_uecc_vli_clear(mod_multiple, word_shift); if (bit_shift > 0) { - for (index = 0; index < (uECC_word_t) num_words; ++index) { - mod_multiple[(uECC_word_t) word_shift + index] = - (uECC_word_t) (mod[index] << bit_shift) | carry; - carry = mod[index] >> (uECC_WORD_BITS - bit_shift); + for (index = 0; index < (mg_uecc_word_t) num_words; ++index) { + mod_multiple[(mg_uecc_word_t) word_shift + index] = + (mg_uecc_word_t) (mod[index] << bit_shift) | carry; + carry = mod[index] >> (MG_UECC_WORD_BITS - bit_shift); } } else { - uECC_vli_set(mod_multiple + word_shift, mod, num_words); + mg_uecc_vli_set(mod_multiple + word_shift, mod, num_words); } for (index = 1; shift >= 0; --shift) { - uECC_word_t borrow = 0; + mg_uecc_word_t borrow = 0; wordcount_t i; for (i = 0; i < num_words * 2; ++i) { - uECC_word_t diff = v[index][i] - mod_multiple[i] - borrow; + mg_uecc_word_t diff = v[index][i] - mod_multiple[i] - borrow; if (diff != v[index][i]) { borrow = (diff > v[index][i]); } v[1 - index][i] = diff; } index = !(index ^ borrow); /* Swap the index if there was no borrow */ - uECC_vli_rshift1(mod_multiple, num_words); + mg_uecc_vli_rshift1(mod_multiple, num_words); mod_multiple[num_words - 1] |= mod_multiple[num_words] - << (uECC_WORD_BITS - 1); - uECC_vli_rshift1(mod_multiple + num_words, num_words); + << (MG_UECC_WORD_BITS - 1); + mg_uecc_vli_rshift1(mod_multiple + num_words, num_words); } - uECC_vli_set(result, v[index], num_words); + mg_uecc_vli_set(result, v[index], num_words); } /* Computes result = (left * right) % mod. */ -uECC_VLI_API void uECC_vli_modMult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} - -uECC_VLI_API void uECC_vli_modMult_fast(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *right, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_mult(product, left, right, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) +MG_UECC_VLI_API void mg_uecc_vli_modMult(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS]; + mg_uecc_vli_mult(product, left, right, num_words); + mg_uecc_vli_mmod(result, product, mod, num_words); +} + +MG_UECC_VLI_API void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + MG_UECC_Curve curve) { + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS]; + mg_uecc_vli_mult(product, left, right, curve->num_words); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) curve->mmod_fast(result, product); #else - uECC_vli_mmod(result, product, curve->p, curve->num_words); + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words); #endif } -#if uECC_SQUARE_FUNC +#if MG_UECC_SQUARE_FUNC -#if uECC_ENABLE_VLI_API +#if MG_UECC_ENABLE_VLI_API /* Computes result = left^2 % mod. */ -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, num_words); - uECC_vli_mmod(result, product, mod, num_words); -} -#endif /* uECC_ENABLE_VLI_API */ - -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_word_t product[2 * uECC_MAX_WORDS]; - uECC_vli_square(product, left, curve->num_words); -#if (uECC_OPTIMIZATION_LEVEL > 0) +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS]; + mg_uecc_vli_square(product, left, num_words); + mg_uecc_vli_mmod(result, product, mod, num_words); +} +#endif /* MG_UECC_ENABLE_VLI_API */ + +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + MG_UECC_Curve curve) { + mg_uecc_word_t product[2 * MG_UECC_MAX_WORDS]; + mg_uecc_vli_square(product, left, curve->num_words); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) curve->mmod_fast(result, product); #else - uECC_vli_mmod(result, product, curve->p, curve->num_words); + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words); #endif } -#else /* uECC_SQUARE_FUNC */ +#else /* MG_UECC_SQUARE_FUNC */ -#if uECC_ENABLE_VLI_API -uECC_VLI_API void uECC_vli_modSquare(uECC_word_t *result, - const uECC_word_t *left, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_vli_modMult(result, left, left, mod, num_words); +#if MG_UECC_ENABLE_VLI_API +MG_UECC_VLI_API void mg_uecc_vli_modSquare(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_vli_modMult(result, left, left, mod, num_words); } -#endif /* uECC_ENABLE_VLI_API */ +#endif /* MG_UECC_ENABLE_VLI_API */ -uECC_VLI_API void uECC_vli_modSquare_fast(uECC_word_t *result, - const uECC_word_t *left, - uECC_Curve curve) { - uECC_vli_modMult_fast(result, left, left, curve); +MG_UECC_VLI_API void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + MG_UECC_Curve curve) { + mg_uecc_vli_modMult_fast(result, left, left, curve); } -#endif /* uECC_SQUARE_FUNC */ +#endif /* MG_UECC_SQUARE_FUNC */ #define EVEN(vli) (!(vli[0] & 1)) -static void vli_modInv_update(uECC_word_t *uv, const uECC_word_t *mod, +static void vli_modInv_update(mg_uecc_word_t *uv, const mg_uecc_word_t *mod, wordcount_t num_words) { - uECC_word_t carry = 0; + mg_uecc_word_t carry = 0; if (!EVEN(uv)) { - carry = uECC_vli_add(uv, uv, mod, num_words); + carry = mg_uecc_vli_add(uv, uv, mod, num_words); } - uECC_vli_rshift1(uv, num_words); + mg_uecc_vli_rshift1(uv, num_words); if (carry) { uv[num_words - 1] |= HIGH_BIT_SET; } @@ -11270,49 +11942,50 @@ static void vli_modInv_update(uECC_word_t *uv, const uECC_word_t *mod, /* Computes result = (1 / input) % mod. All VLIs are the same size. See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" */ -uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, - const uECC_word_t *mod, - wordcount_t num_words) { - uECC_word_t a[uECC_MAX_WORDS], b[uECC_MAX_WORDS], u[uECC_MAX_WORDS], - v[uECC_MAX_WORDS]; +MG_UECC_VLI_API void mg_uecc_vli_modInv(mg_uecc_word_t *result, + const mg_uecc_word_t *input, + const mg_uecc_word_t *mod, + wordcount_t num_words) { + mg_uecc_word_t a[MG_UECC_MAX_WORDS], b[MG_UECC_MAX_WORDS], + u[MG_UECC_MAX_WORDS], v[MG_UECC_MAX_WORDS]; cmpresult_t cmpResult; - if (uECC_vli_isZero(input, num_words)) { - uECC_vli_clear(result, num_words); + if (mg_uecc_vli_isZero(input, num_words)) { + mg_uecc_vli_clear(result, num_words); return; } - uECC_vli_set(a, input, num_words); - uECC_vli_set(b, mod, num_words); - uECC_vli_clear(u, num_words); + mg_uecc_vli_set(a, input, num_words); + mg_uecc_vli_set(b, mod, num_words); + mg_uecc_vli_clear(u, num_words); u[0] = 1; - uECC_vli_clear(v, num_words); - while ((cmpResult = uECC_vli_cmp_unsafe(a, b, num_words)) != 0) { + mg_uecc_vli_clear(v, num_words); + while ((cmpResult = mg_uecc_vli_cmp_unsafe(a, b, num_words)) != 0) { if (EVEN(a)) { - uECC_vli_rshift1(a, num_words); + mg_uecc_vli_rshift1(a, num_words); vli_modInv_update(u, mod, num_words); } else if (EVEN(b)) { - uECC_vli_rshift1(b, num_words); + mg_uecc_vli_rshift1(b, num_words); vli_modInv_update(v, mod, num_words); } else if (cmpResult > 0) { - uECC_vli_sub(a, a, b, num_words); - uECC_vli_rshift1(a, num_words); - if (uECC_vli_cmp_unsafe(u, v, num_words) < 0) { - uECC_vli_add(u, u, mod, num_words); + mg_uecc_vli_sub(a, a, b, num_words); + mg_uecc_vli_rshift1(a, num_words); + if (mg_uecc_vli_cmp_unsafe(u, v, num_words) < 0) { + mg_uecc_vli_add(u, u, mod, num_words); } - uECC_vli_sub(u, u, v, num_words); + mg_uecc_vli_sub(u, u, v, num_words); vli_modInv_update(u, mod, num_words); } else { - uECC_vli_sub(b, b, a, num_words); - uECC_vli_rshift1(b, num_words); - if (uECC_vli_cmp_unsafe(v, u, num_words) < 0) { - uECC_vli_add(v, v, mod, num_words); + mg_uecc_vli_sub(b, b, a, num_words); + mg_uecc_vli_rshift1(b, num_words); + if (mg_uecc_vli_cmp_unsafe(v, u, num_words) < 0) { + mg_uecc_vli_add(v, v, mod, num_words); } - uECC_vli_sub(v, v, u, num_words); + mg_uecc_vli_sub(v, v, u, num_words); vli_modInv_update(v, mod, num_words); } } - uECC_vli_set(result, u, num_words); + mg_uecc_vli_set(result, u, num_words); } /* ------ Point operations ------ */ @@ -11328,7 +12001,7 @@ uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, #define num_bytes_secp256r1 32 #define num_bytes_secp256k1 32 -#if (uECC_WORD_SIZE == 1) +#if (MG_UECC_WORD_SIZE == 1) #define num_words_secp160r1 20 #define num_words_secp192r1 24 @@ -11340,7 +12013,7 @@ uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, 0x##a, 0x##b, 0x##c, 0x##d, 0x##e, 0x##f, 0x##g, 0x##h #define BYTES_TO_WORDS_4(a, b, c, d) 0x##a, 0x##b, 0x##c, 0x##d -#elif (uECC_WORD_SIZE == 4) +#elif (MG_UECC_WORD_SIZE == 4) #define num_words_secp160r1 5 #define num_words_secp192r1 6 @@ -11351,7 +12024,7 @@ uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, #define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##d##c##b##a, 0x##h##g##f##e #define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a -#elif (uECC_WORD_SIZE == 8) +#elif (MG_UECC_WORD_SIZE == 8) #define num_words_secp160r1 3 #define num_words_secp192r1 3 @@ -11362,101 +12035,104 @@ uECC_VLI_API void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, #define BYTES_TO_WORDS_8(a, b, c, d, e, f, g, h) 0x##h##g##f##e##d##c##b##a##U #define BYTES_TO_WORDS_4(a, b, c, d) 0x##d##c##b##a##U -#endif /* uECC_WORD_SIZE */ +#endif /* MG_UECC_WORD_SIZE */ -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp224r1 || uECC_SUPPORTS_secp256r1 -static void double_jacobian_default(uECC_word_t *X1, uECC_word_t *Y1, - uECC_word_t *Z1, uECC_Curve curve) { +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \ + MG_UECC_SUPPORTS_secp224r1 || MG_UECC_SUPPORTS_secp256r1 +static void double_jacobian_default(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *Z1, MG_UECC_Curve curve) { /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[uECC_MAX_WORDS]; - uECC_word_t t5[uECC_MAX_WORDS]; + mg_uecc_word_t t4[MG_UECC_MAX_WORDS]; + mg_uecc_word_t t5[MG_UECC_MAX_WORDS]; wordcount_t num_words = curve->num_words; - if (uECC_vli_isZero(Z1, num_words)) { + if (mg_uecc_vli_isZero(Z1, num_words)) { return; } - uECC_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */ - uECC_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */ - uECC_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */ - uECC_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */ - - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */ - uECC_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */ - uECC_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */ - uECC_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 - z1^4 */ - - uECC_vli_modAdd(Z1, X1, X1, curve->p, num_words); /* t3 = 2*(x1^2 - z1^4) */ - uECC_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = 3*(x1^2 - z1^4) */ - if (uECC_vli_testBit(X1, 0)) { - uECC_word_t l_carry = uECC_vli_add(X1, X1, curve->p, num_words); - uECC_vli_rshift1(X1, num_words); - X1[num_words - 1] |= l_carry << (uECC_WORD_BITS - 1); + mg_uecc_vli_modSquare_fast(t4, Y1, curve); /* t4 = y1^2 */ + mg_uecc_vli_modMult_fast(t5, X1, t4, curve); /* t5 = x1*y1^2 = A */ + mg_uecc_vli_modSquare_fast(t4, t4, curve); /* t4 = y1^4 */ + mg_uecc_vli_modMult_fast(Y1, Y1, Z1, curve); /* t2 = y1*z1 = z3 */ + mg_uecc_vli_modSquare_fast(Z1, Z1, curve); /* t3 = z1^2 */ + + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, num_words); /* t1 = x1 + z1^2 */ + mg_uecc_vli_modAdd(Z1, Z1, Z1, curve->p, num_words); /* t3 = 2*z1^2 */ + mg_uecc_vli_modSub(Z1, X1, Z1, curve->p, num_words); /* t3 = x1 - z1^2 */ + mg_uecc_vli_modMult_fast(X1, X1, Z1, curve); /* t1 = x1^2 - z1^4 */ + + mg_uecc_vli_modAdd(Z1, X1, X1, curve->p, + num_words); /* t3 = 2*(x1^2 - z1^4) */ + mg_uecc_vli_modAdd(X1, X1, Z1, curve->p, + num_words); /* t1 = 3*(x1^2 - z1^4) */ + if (mg_uecc_vli_testBit(X1, 0)) { + mg_uecc_word_t l_carry = mg_uecc_vli_add(X1, X1, curve->p, num_words); + mg_uecc_vli_rshift1(X1, num_words); + X1[num_words - 1] |= l_carry << (MG_UECC_WORD_BITS - 1); } else { - uECC_vli_rshift1(X1, num_words); + mg_uecc_vli_rshift1(X1, num_words); } /* t1 = 3/2*(x1^2 - z1^4) = B */ - uECC_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */ - uECC_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */ - uECC_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A - x3) */ - uECC_vli_modSub(t4, X1, t4, curve->p, - num_words); /* t4 = B * (A - x3) - y1^4 = y3 */ + mg_uecc_vli_modSquare_fast(Z1, X1, curve); /* t3 = B^2 */ + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - A */ + mg_uecc_vli_modSub(Z1, Z1, t5, curve->p, num_words); /* t3 = B^2 - 2A = x3 */ + mg_uecc_vli_modSub(t5, t5, Z1, curve->p, num_words); /* t5 = A - x3 */ + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = B * (A - x3) */ + mg_uecc_vli_modSub(t4, X1, t4, curve->p, + num_words); /* t4 = B * (A - x3) - y1^4 = y3 */ - uECC_vli_set(X1, Z1, num_words); - uECC_vli_set(Z1, Y1, num_words); - uECC_vli_set(Y1, t4, num_words); + mg_uecc_vli_set(X1, Z1, num_words); + mg_uecc_vli_set(Z1, Y1, num_words); + mg_uecc_vli_set(Y1, t4, num_words); } /* Computes result = x^3 + ax + b. result must not overlap x. */ -static void x_side_default(uECC_word_t *result, const uECC_word_t *x, - uECC_Curve curve) { - uECC_word_t _3[uECC_MAX_WORDS] = {3}; /* -a = 3 */ +static void x_side_default(mg_uecc_word_t *result, const mg_uecc_word_t *x, + MG_UECC_Curve curve) { + mg_uecc_word_t _3[MG_UECC_MAX_WORDS] = {3}; /* -a = 3 */ wordcount_t num_words = curve->num_words; - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */ - uECC_vli_modAdd(result, result, curve->b, curve->p, - num_words); /* r = x^3 - 3x + b */ + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */ + mg_uecc_vli_modSub(result, result, _3, curve->p, num_words); /* r = x^2 - 3 */ + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 - 3x */ + mg_uecc_vli_modAdd(result, result, curve->b, curve->p, + num_words); /* r = x^3 - 3x + b */ } -#endif /* uECC_SUPPORTS_secp... */ +#endif /* MG_UECC_SUPPORTS_secp... */ -#if uECC_SUPPORT_COMPRESSED_POINT -#if uECC_SUPPORTS_secp160r1 || uECC_SUPPORTS_secp192r1 || \ - uECC_SUPPORTS_secp256r1 || uECC_SUPPORTS_secp256k1 +#if MG_UECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORTS_secp160r1 || MG_UECC_SUPPORTS_secp192r1 || \ + MG_UECC_SUPPORTS_secp256r1 || MG_UECC_SUPPORTS_secp256k1 /* Compute a = sqrt(a) (mod curve_p). */ -static void mod_sqrt_default(uECC_word_t *a, uECC_Curve curve) { +static void mod_sqrt_default(mg_uecc_word_t *a, MG_UECC_Curve curve) { bitcount_t i; - uECC_word_t p1[uECC_MAX_WORDS] = {1}; - uECC_word_t l_result[uECC_MAX_WORDS] = {1}; + mg_uecc_word_t p1[MG_UECC_MAX_WORDS] = {1}; + mg_uecc_word_t l_result[MG_UECC_MAX_WORDS] = {1}; wordcount_t num_words = curve->num_words; /* When curve->p == 3 (mod 4), we can compute sqrt(a) = a^((curve->p + 1) / 4) (mod curve->p). */ - uECC_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */ - for (i = uECC_vli_numBits(p1, num_words) - 1; i > 1; --i) { - uECC_vli_modSquare_fast(l_result, l_result, curve); - if (uECC_vli_testBit(p1, i)) { - uECC_vli_modMult_fast(l_result, l_result, a, curve); + mg_uecc_vli_add(p1, curve->p, p1, num_words); /* p1 = curve_p + 1 */ + for (i = mg_uecc_vli_numBits(p1, num_words) - 1; i > 1; --i) { + mg_uecc_vli_modSquare_fast(l_result, l_result, curve); + if (mg_uecc_vli_testBit(p1, i)) { + mg_uecc_vli_modMult_fast(l_result, l_result, a, curve); } } - uECC_vli_set(a, l_result, num_words); + mg_uecc_vli_set(a, l_result, num_words); } -#endif /* uECC_SUPPORTS_secp... */ -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ +#endif /* MG_UECC_SUPPORTS_secp... */ +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */ -#if uECC_SUPPORTS_secp160r1 +#if MG_UECC_SUPPORTS_secp160r1 -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result, + mg_uecc_word_t *product); #endif -static const struct uECC_Curve_t curve_secp160r1 = { +static const struct MG_UECC_Curve_t curve_secp160r1 = { num_words_secp160r1, num_bytes_secp160r1, 161, /* num_n_bits */ @@ -11477,32 +12153,34 @@ static const struct uECC_Curve_t curve_secp160r1 = { BYTES_TO_WORDS_8(9F, F8, AC, 65, 8B, 7A, BD, 54), BYTES_TO_WORDS_4(FC, BE, 97, 1C)}, &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT &mod_sqrt_default, #endif &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) &vli_mmod_fast_secp160r1 #endif }; -uECC_Curve uECC_secp160r1(void) { +MG_UECC_Curve mg_uecc_secp160r1(void) { return &curve_secp160r1; } -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) /* Computes result = product % curve_p see http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf page 354 Note that this only works if log2(omega) < log2(p) / 2 */ -static void omega_mult_secp160r1(uECC_word_t *result, const uECC_word_t *right); -#if uECC_WORD_SIZE == 8 -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t copy; +static void omega_mult_secp160r1(mg_uecc_word_t *result, + const mg_uecc_word_t *right); +#if MG_UECC_WORD_SIZE == 8 +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result, + mg_uecc_word_t *product) { + mg_uecc_word_t tmp[2 * num_words_secp160r1]; + mg_uecc_word_t copy; - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); + mg_uecc_vli_clear(tmp, num_words_secp160r1); + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); omega_mult_secp160r1(tmp, product + num_words_secp160r1 - 1); /* (Rq, q) = q * c */ @@ -11510,16 +12188,17 @@ static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { product[num_words_secp160r1 - 1] &= 0xffffffff; copy = tmp[num_words_secp160r1 - 1]; tmp[num_words_secp160r1 - 1] &= 0xffffffff; - uECC_vli_add(result, product, tmp, num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); + mg_uecc_vli_add(result, product, tmp, + num_words_secp160r1); /* (C, r) = r + q */ + mg_uecc_vli_clear(product, num_words_secp160r1); tmp[num_words_secp160r1 - 1] = copy; omega_mult_secp160r1(product, tmp + num_words_secp160r1 - 1); /* Rq*c */ - uECC_vli_add(result, result, product, - num_words_secp160r1); /* (C1, r) = r + Rq*c */ + mg_uecc_vli_add(result, result, product, + num_words_secp160r1); /* (C1, r) = r + Rq*c */ - while (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > - 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); + while (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p, + num_words_secp160r1) > 0) { + mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); } } @@ -11537,81 +12216,84 @@ static void omega_mult_secp160r1(uint64_t *result, const uint64_t *right) { result[i] = carry; } #else -static void vli_mmod_fast_secp160r1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp160r1]; - uECC_word_t carry; +static void vli_mmod_fast_secp160r1(mg_uecc_word_t *result, + mg_uecc_word_t *product) { + mg_uecc_word_t tmp[2 * num_words_secp160r1]; + mg_uecc_word_t carry; - uECC_vli_clear(tmp, num_words_secp160r1); - uECC_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); + mg_uecc_vli_clear(tmp, num_words_secp160r1); + mg_uecc_vli_clear(tmp + num_words_secp160r1, num_words_secp160r1); omega_mult_secp160r1(tmp, product + num_words_secp160r1); /* (Rq, q) = q * c */ - carry = uECC_vli_add(result, product, tmp, - num_words_secp160r1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp160r1); + carry = mg_uecc_vli_add(result, product, tmp, + num_words_secp160r1); /* (C, r) = r + q */ + mg_uecc_vli_clear(product, num_words_secp160r1); omega_mult_secp160r1(product, tmp + num_words_secp160r1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, - num_words_secp160r1); /* (C1, r) = r + Rq*c */ + carry += mg_uecc_vli_add(result, result, product, + num_words_secp160r1); /* (C1, r) = r + Rq*c */ while (carry > 0) { --carry; - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); + mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); } - if (uECC_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > 0) { - uECC_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); + if (mg_uecc_vli_cmp_unsafe(result, curve_secp160r1.p, num_words_secp160r1) > + 0) { + mg_uecc_vli_sub(result, result, curve_secp160r1.p, num_words_secp160r1); } } #endif -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 static void omega_mult_secp160r1(uint8_t *result, const uint8_t *right) { uint8_t carry; uint8_t i; /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */ + mg_uecc_vli_set(result + 4, right, num_words_secp160r1); /* 2^32 */ + mg_uecc_vli_rshift1(result + 4, num_words_secp160r1); /* 2^31 */ result[3] = right[0] << 7; /* get last bit from shift */ - carry = - uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ + carry = mg_uecc_vli_add(result, result, right, + num_words_secp160r1); /* 2^31 + 1 */ for (i = num_words_secp160r1; carry; ++i) { uint16_t sum = (uint16_t) result[i] + carry; result[i] = (uint8_t) sum; carry = sum >> 8; } } -#elif uECC_WORD_SIZE == 4 +#elif MG_UECC_WORD_SIZE == 4 static void omega_mult_secp160r1(uint32_t *result, const uint32_t *right) { uint32_t carry; unsigned i; /* Multiply by (2^31 + 1). */ - uECC_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */ - uECC_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */ + mg_uecc_vli_set(result + 1, right, num_words_secp160r1); /* 2^32 */ + mg_uecc_vli_rshift1(result + 1, num_words_secp160r1); /* 2^31 */ result[0] = right[0] << 31; /* get last bit from shift */ - carry = - uECC_vli_add(result, result, right, num_words_secp160r1); /* 2^31 + 1 */ + carry = mg_uecc_vli_add(result, result, right, + num_words_secp160r1); /* 2^31 + 1 */ for (i = num_words_secp160r1; carry; ++i) { uint64_t sum = (uint64_t) result[i] + carry; result[i] = (uint32_t) sum; carry = sum >> 32; } } -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */ +#endif /* MG_UECC_WORD_SIZE */ +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp160r1) */ -#endif /* uECC_SUPPORTS_secp160r1 */ +#endif /* MG_UECC_SUPPORTS_secp160r1 */ -#if uECC_SUPPORTS_secp192r1 +#if MG_UECC_SUPPORTS_secp192r1 -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp192r1(uECC_word_t *result, uECC_word_t *product); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) +static void vli_mmod_fast_secp192r1(mg_uecc_word_t *result, + mg_uecc_word_t *product); #endif -static const struct uECC_Curve_t curve_secp192r1 = { +static const struct MG_UECC_Curve_t curve_secp192r1 = { num_words_secp192r1, num_bytes_secp192r1, 192, /* num_n_bits */ @@ -11632,32 +12314,32 @@ static const struct uECC_Curve_t curve_secp192r1 = { BYTES_TO_WORDS_8(49, 30, 24, 72, AB, E9, A7, 0F), BYTES_TO_WORDS_8(E7, 80, 9C, E5, 19, 05, 21, 64)}, &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT &mod_sqrt_default, #endif &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) &vli_mmod_fast_secp192r1 #endif }; -uECC_Curve uECC_secp192r1(void) { +MG_UECC_Curve mg_uecc_secp192r1(void) { return &curve_secp192r1; } -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) /* Computes result = product % curve_p. See algorithm 5 and 6 from http://www.isys.uni-klu.ac.at/PDF/2001-0126-MT.pdf */ -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) { uint8_t tmp[num_words_secp192r1]; uint8_t carry; - uECC_vli_set(result, product, num_words_secp192r1); + mg_uecc_vli_set(result, product, num_words_secp192r1); - uECC_vli_set(tmp, &product[24], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); + mg_uecc_vli_set(tmp, &product[24], num_words_secp192r1); + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = tmp[1] = tmp[2] = tmp[3] = tmp[4] = tmp[5] = tmp[6] = tmp[7] = 0; tmp[8] = product[24]; @@ -11676,7 +12358,7 @@ static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) { tmp[21] = product[37]; tmp[22] = product[38]; tmp[23] = product[39]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = tmp[8] = product[40]; tmp[1] = tmp[9] = product[41]; @@ -11688,40 +12370,40 @@ static void vli_mmod_fast_secp192r1(uint8_t *result, uint8_t *product) { tmp[7] = tmp[15] = product[47]; tmp[16] = tmp[17] = tmp[18] = tmp[19] = tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, - num_words_secp192r1) != 1) { + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result, + num_words_secp192r1) != 1) { carry -= - uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); + mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); } } -#elif uECC_WORD_SIZE == 4 +#elif MG_UECC_WORD_SIZE == 4 static void vli_mmod_fast_secp192r1(uint32_t *result, uint32_t *product) { uint32_t tmp[num_words_secp192r1]; int carry; - uECC_vli_set(result, product, num_words_secp192r1); + mg_uecc_vli_set(result, product, num_words_secp192r1); - uECC_vli_set(tmp, &product[6], num_words_secp192r1); - carry = uECC_vli_add(result, result, tmp, num_words_secp192r1); + mg_uecc_vli_set(tmp, &product[6], num_words_secp192r1); + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = tmp[1] = 0; tmp[2] = product[6]; tmp[3] = product[7]; tmp[4] = product[8]; tmp[5] = product[9]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = tmp[2] = product[10]; tmp[1] = tmp[3] = product[11]; tmp[4] = tmp[5] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, - num_words_secp192r1) != 1) { + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result, + num_words_secp192r1) != 1) { carry -= - uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); + mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); } } #else @@ -11729,41 +12411,42 @@ static void vli_mmod_fast_secp192r1(uint64_t *result, uint64_t *product) { uint64_t tmp[num_words_secp192r1]; int carry; - uECC_vli_set(result, product, num_words_secp192r1); + mg_uecc_vli_set(result, product, num_words_secp192r1); - uECC_vli_set(tmp, &product[3], num_words_secp192r1); - carry = (int) uECC_vli_add(result, result, tmp, num_words_secp192r1); + mg_uecc_vli_set(tmp, &product[3], num_words_secp192r1); + carry = (int) mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = 0; tmp[1] = product[3]; tmp[2] = product[4]; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); tmp[0] = tmp[1] = product[5]; tmp[2] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp192r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp192r1); - while (carry || uECC_vli_cmp_unsafe(curve_secp192r1.p, result, - num_words_secp192r1) != 1) { + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp192r1.p, result, + num_words_secp192r1) != 1) { carry -= - uECC_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); + mg_uecc_vli_sub(result, result, curve_secp192r1.p, num_words_secp192r1); } } -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ +#endif /* MG_UECC_WORD_SIZE */ +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */ -#endif /* uECC_SUPPORTS_secp192r1 */ +#endif /* MG_UECC_SUPPORTS_secp192r1 */ -#if uECC_SUPPORTS_secp224r1 +#if MG_UECC_SUPPORTS_secp224r1 -#if uECC_SUPPORT_COMPRESSED_POINT -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve); +#if MG_UECC_SUPPORT_COMPRESSED_POINT +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve); #endif -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp224r1(uECC_word_t *result, uECC_word_t *product); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) +static void vli_mmod_fast_secp224r1(mg_uecc_word_t *result, + mg_uecc_word_t *product); #endif -static const struct uECC_Curve_t curve_secp224r1 = { +static const struct MG_UECC_Curve_t curve_secp224r1 = { num_words_secp224r1, num_bytes_secp224r1, 224, /* num_n_bits */ @@ -11789,106 +12472,108 @@ static const struct uECC_Curve_t curve_secp224r1 = { BYTES_TO_WORDS_8(56, 32, 41, F5, AB, B3, 04, 0C), BYTES_TO_WORDS_4(85, 0A, 05, B4)}, &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT &mod_sqrt_secp224r1, #endif &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) &vli_mmod_fast_secp224r1 #endif }; -uECC_Curve uECC_secp224r1(void) { +MG_UECC_Curve mg_uecc_secp224r1(void) { return &curve_secp224r1; } -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT /* Routine 3.2.4 RS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rs(uECC_word_t *d1, uECC_word_t *e1, - uECC_word_t *f1, const uECC_word_t *d0, - const uECC_word_t *e0, - const uECC_word_t *f0) { - uECC_word_t t[num_words_secp224r1]; - - uECC_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <-- d0 ^ 2 */ - uECC_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */ - uECC_vli_modAdd(d1, t, f0, curve_secp224r1.p, - num_words_secp224r1); /* d1 <-- t + f0 */ - uECC_vli_modAdd(e1, e1, e1, curve_secp224r1.p, - num_words_secp224r1); /* e1 <-- e1 + e1 */ - uECC_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t * f0 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, - num_words_secp224r1); /* f1 <-- f1 + f1 */ - uECC_vli_modAdd(f1, f1, f1, curve_secp224r1.p, - num_words_secp224r1); /* f1 <-- f1 + f1 */ +static void mod_sqrt_secp224r1_rs(mg_uecc_word_t *d1, mg_uecc_word_t *e1, + mg_uecc_word_t *f1, const mg_uecc_word_t *d0, + const mg_uecc_word_t *e0, + const mg_uecc_word_t *f0) { + mg_uecc_word_t t[num_words_secp224r1]; + + mg_uecc_vli_modSquare_fast(t, d0, &curve_secp224r1); /* t <-- d0 ^ 2 */ + mg_uecc_vli_modMult_fast(e1, d0, e0, &curve_secp224r1); /* e1 <-- d0 * e0 */ + mg_uecc_vli_modAdd(d1, t, f0, curve_secp224r1.p, + num_words_secp224r1); /* d1 <-- t + f0 */ + mg_uecc_vli_modAdd(e1, e1, e1, curve_secp224r1.p, + num_words_secp224r1); /* e1 <-- e1 + e1 */ + mg_uecc_vli_modMult_fast(f1, t, f0, &curve_secp224r1); /* f1 <-- t * f0 */ + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p, + num_words_secp224r1); /* f1 <-- f1 + f1 */ + mg_uecc_vli_modAdd(f1, f1, f1, curve_secp224r1.p, + num_words_secp224r1); /* f1 <-- f1 + f1 */ } /* Routine 3.2.5 RSS; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rss(uECC_word_t *d1, uECC_word_t *e1, - uECC_word_t *f1, const uECC_word_t *d0, - const uECC_word_t *e0, const uECC_word_t *f0, +static void mod_sqrt_secp224r1_rss(mg_uecc_word_t *d1, mg_uecc_word_t *e1, + mg_uecc_word_t *f1, const mg_uecc_word_t *d0, + const mg_uecc_word_t *e0, + const mg_uecc_word_t *f0, const bitcount_t j) { bitcount_t i; - uECC_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */ - uECC_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */ - uECC_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */ + mg_uecc_vli_set(d1, d0, num_words_secp224r1); /* d1 <-- d0 */ + mg_uecc_vli_set(e1, e0, num_words_secp224r1); /* e1 <-- e0 */ + mg_uecc_vli_set(f1, f0, num_words_secp224r1); /* f1 <-- f0 */ for (i = 1; i <= j; i++) { mod_sqrt_secp224r1_rs(d1, e1, f1, d1, e1, f1); /* RS (d1,e1,f1,d1,e1,f1) */ } } /* Routine 3.2.6 RM; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rm(uECC_word_t *d2, uECC_word_t *e2, - uECC_word_t *f2, const uECC_word_t *c, - const uECC_word_t *d0, const uECC_word_t *e0, - const uECC_word_t *d1, - const uECC_word_t *e1) { - uECC_word_t t1[num_words_secp224r1]; - uECC_word_t t2[num_words_secp224r1]; - - uECC_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */ - uECC_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1 <-- t1 * c */ +static void mod_sqrt_secp224r1_rm(mg_uecc_word_t *d2, mg_uecc_word_t *e2, + mg_uecc_word_t *f2, const mg_uecc_word_t *c, + const mg_uecc_word_t *d0, + const mg_uecc_word_t *e0, + const mg_uecc_word_t *d1, + const mg_uecc_word_t *e1) { + mg_uecc_word_t t1[num_words_secp224r1]; + mg_uecc_word_t t2[num_words_secp224r1]; + + mg_uecc_vli_modMult_fast(t1, e0, e1, &curve_secp224r1); /* t1 <-- e0 * e1 */ + mg_uecc_vli_modMult_fast(t1, t1, c, &curve_secp224r1); /* t1 <-- t1 * c */ /* t1 <-- p - t1 */ - uECC_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p, - num_words_secp224r1); - uECC_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */ - uECC_vli_modAdd(t2, t2, t1, curve_secp224r1.p, - num_words_secp224r1); /* t2 <-- t2 + t1 */ - uECC_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */ - uECC_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */ - uECC_vli_modAdd(e2, e2, t1, curve_secp224r1.p, - num_words_secp224r1); /* e2 <-- e2 + t1 */ - uECC_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <-- e2^2 */ - uECC_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */ + mg_uecc_vli_modSub(t1, curve_secp224r1.p, t1, curve_secp224r1.p, + num_words_secp224r1); + mg_uecc_vli_modMult_fast(t2, d0, d1, &curve_secp224r1); /* t2 <-- d0 * d1 */ + mg_uecc_vli_modAdd(t2, t2, t1, curve_secp224r1.p, + num_words_secp224r1); /* t2 <-- t2 + t1 */ + mg_uecc_vli_modMult_fast(t1, d0, e1, &curve_secp224r1); /* t1 <-- d0 * e1 */ + mg_uecc_vli_modMult_fast(e2, d1, e0, &curve_secp224r1); /* e2 <-- d1 * e0 */ + mg_uecc_vli_modAdd(e2, e2, t1, curve_secp224r1.p, + num_words_secp224r1); /* e2 <-- e2 + t1 */ + mg_uecc_vli_modSquare_fast(f2, e2, &curve_secp224r1); /* f2 <-- e2^2 */ + mg_uecc_vli_modMult_fast(f2, f2, c, &curve_secp224r1); /* f2 <-- f2 * c */ /* f2 <-- p - f2 */ - uECC_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p, - num_words_secp224r1); - uECC_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */ + mg_uecc_vli_modSub(f2, curve_secp224r1.p, f2, curve_secp224r1.p, + num_words_secp224r1); + mg_uecc_vli_set(d2, t2, num_words_secp224r1); /* d2 <-- t2 */ } /* Routine 3.2.7 RP; from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1_rp(uECC_word_t *d1, uECC_word_t *e1, - uECC_word_t *f1, const uECC_word_t *c, - const uECC_word_t *r) { +static void mod_sqrt_secp224r1_rp(mg_uecc_word_t *d1, mg_uecc_word_t *e1, + mg_uecc_word_t *f1, const mg_uecc_word_t *c, + const mg_uecc_word_t *r) { wordcount_t i; wordcount_t pow2i = 1; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */ - uECC_word_t f0[num_words_secp224r1]; + mg_uecc_word_t d0[num_words_secp224r1]; + mg_uecc_word_t e0[num_words_secp224r1] = {1}; /* e0 <-- 1 */ + mg_uecc_word_t f0[num_words_secp224r1]; - uECC_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */ + mg_uecc_vli_set(d0, r, num_words_secp224r1); /* d0 <-- r */ /* f0 <-- p - c */ - uECC_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p, - num_words_secp224r1); + mg_uecc_vli_modSub(f0, curve_secp224r1.p, c, curve_secp224r1.p, + num_words_secp224r1); for (i = 0; i <= 6; i++) { mod_sqrt_secp224r1_rss(d1, e1, f1, d0, e0, f0, pow2i); /* RSS (d1,e1,f1,d0,e0,f0,2^i) */ mod_sqrt_secp224r1_rm(d1, e1, f1, c, d1, e1, d0, - e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */ - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ + e0); /* RM (d1,e1,f1,c,d1,e1,d0,e0) */ + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ pow2i *= 2; } } @@ -11896,46 +12581,46 @@ static void mod_sqrt_secp224r1_rp(uECC_word_t *d1, uECC_word_t *e1, /* Compute a = sqrt(a) (mod curve_p). */ /* Routine 3.2.8 mp_mod_sqrt_224; from * http://www.nsa.gov/ia/_files/nist-routines.pdf */ -static void mod_sqrt_secp224r1(uECC_word_t *a, uECC_Curve curve) { +static void mod_sqrt_secp224r1(mg_uecc_word_t *a, MG_UECC_Curve curve) { (void) curve; bitcount_t i; - uECC_word_t e1[num_words_secp224r1]; - uECC_word_t f1[num_words_secp224r1]; - uECC_word_t d0[num_words_secp224r1]; - uECC_word_t e0[num_words_secp224r1]; - uECC_word_t f0[num_words_secp224r1]; - uECC_word_t d1[num_words_secp224r1]; + mg_uecc_word_t e1[num_words_secp224r1]; + mg_uecc_word_t f1[num_words_secp224r1]; + mg_uecc_word_t d0[num_words_secp224r1]; + mg_uecc_word_t e0[num_words_secp224r1]; + mg_uecc_word_t f0[num_words_secp224r1]; + mg_uecc_word_t d1[num_words_secp224r1]; /* s = a; using constant instead of random value */ mod_sqrt_secp224r1_rp(d0, e0, f0, a, a); /* RP (d0, e0, f0, c, s) */ mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ for (i = 1; i <= 95; i++) { - uECC_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ - uECC_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ - uECC_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ + mg_uecc_vli_set(d0, d1, num_words_secp224r1); /* d0 <-- d1 */ + mg_uecc_vli_set(e0, e1, num_words_secp224r1); /* e0 <-- e1 */ + mg_uecc_vli_set(f0, f1, num_words_secp224r1); /* f0 <-- f1 */ mod_sqrt_secp224r1_rs(d1, e1, f1, d0, e0, f0); /* RS (d1, e1, f1, d0, e0, f0) */ - if (uECC_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */ + if (mg_uecc_vli_isZero(d1, num_words_secp224r1)) { /* if d1 == 0 */ break; } } - uECC_vli_modInv(f1, e0, curve_secp224r1.p, - num_words_secp224r1); /* f1 <-- 1 / e0 */ - uECC_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <-- d0 / e0 */ + mg_uecc_vli_modInv(f1, e0, curve_secp224r1.p, + num_words_secp224r1); /* f1 <-- 1 / e0 */ + mg_uecc_vli_modMult_fast(a, d0, f1, &curve_secp224r1); /* a <-- d0 / e0 */ } -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */ -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) /* Computes result = product % curve_p from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { uint8_t tmp[num_words_secp224r1]; int8_t carry; /* t */ - uECC_vli_set(result, product, num_words_secp224r1); + mg_uecc_vli_set(result, product, num_words_secp224r1); /* s1 */ tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; @@ -11957,7 +12642,7 @@ static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { tmp[25] = product[41]; tmp[26] = product[42]; tmp[27] = product[43]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* s2 */ tmp[12] = product[44]; @@ -11973,7 +12658,7 @@ static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { tmp[22] = product[54]; tmp[23] = product[55]; tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* d1 */ tmp[0] = product[28]; @@ -12004,7 +12689,7 @@ static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { tmp[25] = product[53]; tmp[26] = product[54]; tmp[27] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); /* d2 */ tmp[0] = product[44]; @@ -12023,28 +12708,28 @@ static void vli_mmod_fast_secp224r1(uint8_t *result, uint8_t *product) { tmp[16] = tmp[17] = tmp[18] = tmp[19] = 0; tmp[20] = tmp[21] = tmp[22] = tmp[23] = 0; tmp[24] = tmp[25] = tmp[26] = tmp[27] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); if (carry < 0) { do { - carry += - uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p, + num_words_secp224r1); } while (carry < 0); } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, - num_words_secp224r1) != 1) { - carry -= - uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result, + num_words_secp224r1) != 1) { + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p, + num_words_secp224r1); } } } -#elif uECC_WORD_SIZE == 4 +#elif MG_UECC_WORD_SIZE == 4 static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) { uint32_t tmp[num_words_secp224r1]; int carry; /* t */ - uECC_vli_set(result, product, num_words_secp224r1); + mg_uecc_vli_set(result, product, num_words_secp224r1); /* s1 */ tmp[0] = tmp[1] = tmp[2] = 0; @@ -12052,14 +12737,14 @@ static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) { tmp[4] = product[8]; tmp[5] = product[9]; tmp[6] = product[10]; - carry = uECC_vli_add(result, result, tmp, num_words_secp224r1); + carry = mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* s2 */ tmp[3] = product[11]; tmp[4] = product[12]; tmp[5] = product[13]; tmp[6] = 0; - carry += uECC_vli_add(result, result, tmp, num_words_secp224r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* d1 */ tmp[0] = product[7]; @@ -12069,25 +12754,25 @@ static void vli_mmod_fast_secp224r1(uint32_t *result, uint32_t *product) { tmp[4] = product[11]; tmp[5] = product[12]; tmp[6] = product[13]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); /* d2 */ tmp[0] = product[11]; tmp[1] = product[12]; tmp[2] = product[13]; tmp[3] = tmp[4] = tmp[5] = tmp[6] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); if (carry < 0) { do { - carry += - uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p, + num_words_secp224r1); } while (carry < 0); } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp224r1.p, result, - num_words_secp224r1) != 1) { - carry -= - uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result, + num_words_secp224r1) != 1) { + carry -= mg_uecc_vli_sub(result, result, curve_secp224r1.p, + num_words_secp224r1); } } } @@ -12097,7 +12782,7 @@ static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) { int carry = 0; /* t */ - uECC_vli_set(result, product, num_words_secp224r1); + mg_uecc_vli_set(result, product, num_words_secp224r1); result[num_words_secp224r1 - 1] &= 0xffffffff; /* s1 */ @@ -12105,51 +12790,52 @@ static void vli_mmod_fast_secp224r1(uint64_t *result, uint64_t *product) { tmp[1] = product[3] & 0xffffffff00000000ull; tmp[2] = product[4]; tmp[3] = product[5] & 0xffffffff; - uECC_vli_add(result, result, tmp, num_words_secp224r1); + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* s2 */ tmp[1] = product[5] & 0xffffffff00000000ull; tmp[2] = product[6]; tmp[3] = 0; - uECC_vli_add(result, result, tmp, num_words_secp224r1); + mg_uecc_vli_add(result, result, tmp, num_words_secp224r1); /* d1 */ tmp[0] = (product[3] >> 32) | (product[4] << 32); tmp[1] = (product[4] >> 32) | (product[5] << 32); tmp[2] = (product[5] >> 32) | (product[6] << 32); tmp[3] = product[6] >> 32; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); /* d2 */ tmp[0] = (product[5] >> 32) | (product[6] << 32); tmp[1] = product[6] >> 32; tmp[2] = tmp[3] = 0; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp224r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp224r1); if (carry < 0) { do { - carry += - uECC_vli_add(result, result, curve_secp224r1.p, num_words_secp224r1); + carry += mg_uecc_vli_add(result, result, curve_secp224r1.p, + num_words_secp224r1); } while (carry < 0); } else { - while (uECC_vli_cmp_unsafe(curve_secp224r1.p, result, - num_words_secp224r1) != 1) { - uECC_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); + while (mg_uecc_vli_cmp_unsafe(curve_secp224r1.p, result, + num_words_secp224r1) != 1) { + mg_uecc_vli_sub(result, result, curve_secp224r1.p, num_words_secp224r1); } } } -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0) */ +#endif /* MG_UECC_WORD_SIZE */ +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0) */ -#endif /* uECC_SUPPORTS_secp224r1 */ +#endif /* MG_UECC_SUPPORTS_secp224r1 */ -#if uECC_SUPPORTS_secp256r1 +#if MG_UECC_SUPPORTS_secp256r1 -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256r1(uECC_word_t *result, uECC_word_t *product); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) +static void vli_mmod_fast_secp256r1(mg_uecc_word_t *result, + mg_uecc_word_t *product); #endif -static const struct uECC_Curve_t curve_secp256r1 = { +static const struct MG_UECC_Curve_t curve_secp256r1 = { num_words_secp256r1, num_bytes_secp256r1, 256, /* num_n_bits */ @@ -12175,29 +12861,29 @@ static const struct uECC_Curve_t curve_secp256r1 = { BYTES_TO_WORDS_8(BC, 86, 98, 76, 55, BD, EB, B3), BYTES_TO_WORDS_8(E7, 93, 3A, AA, D8, 35, C6, 5A)}, &double_jacobian_default, -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT &mod_sqrt_default, #endif &x_side_default, -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) &vli_mmod_fast_secp256r1 #endif }; -uECC_Curve uECC_secp256r1(void) { +MG_UECC_Curve mg_uecc_secp256r1(void) { return &curve_secp256r1; } -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) /* Computes result = product % curve_p from http://www.nsa.gov/ia/_files/nist-routines.pdf */ -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { uint8_t tmp[num_words_secp256r1]; int8_t carry; /* t */ - uECC_vli_set(result, product, num_words_secp256r1); + mg_uecc_vli_set(result, product, num_words_secp256r1); /* s1 */ tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; @@ -12223,8 +12909,8 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry = uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry = mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s2 */ tmp[12] = product[48]; @@ -12244,8 +12930,8 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[26] = product[62]; tmp[27] = product[63]; tmp[28] = tmp[29] = tmp[30] = tmp[31] = 0; - carry += uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s3 */ tmp[0] = product[32]; @@ -12271,7 +12957,7 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[61]; tmp[30] = product[62]; tmp[31] = product[63]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s4 */ tmp[0] = product[36]; @@ -12306,7 +12992,7 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[33]; tmp[30] = product[34]; tmp[31] = product[35]; - carry += uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* d1 */ tmp[0] = product[44]; @@ -12332,7 +13018,7 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[41]; tmp[30] = product[42]; tmp[31] = product[43]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d2 */ tmp[0] = product[48]; @@ -12361,7 +13047,7 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[45]; tmp[30] = product[46]; tmp[31] = product[47]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d3 */ tmp[0] = product[52]; @@ -12393,7 +13079,7 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[49]; tmp[30] = product[50]; tmp[31] = product[51]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d4 */ tmp[0] = product[56]; @@ -12422,28 +13108,28 @@ static void vli_mmod_fast_secp256r1(uint8_t *result, uint8_t *product) { tmp[29] = product[53]; tmp[30] = product[54]; tmp[31] = product[55]; - carry -= uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); if (carry < 0) { do { - carry += - uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); + carry += mg_uecc_vli_add(result, result, curve_secp256r1.p, + num_words_secp256r1); } while (carry < 0); } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, - num_words_secp256r1) != 1) { - carry -= - uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result, + num_words_secp256r1) != 1) { + carry -= mg_uecc_vli_sub(result, result, curve_secp256r1.p, + num_words_secp256r1); } } } -#elif uECC_WORD_SIZE == 4 +#elif MG_UECC_WORD_SIZE == 4 static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { uint32_t tmp[num_words_secp256r1]; int carry; /* t */ - uECC_vli_set(result, product, num_words_secp256r1); + mg_uecc_vli_set(result, product, num_words_secp256r1); /* s1 */ tmp[0] = tmp[1] = tmp[2] = 0; @@ -12452,8 +13138,8 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[5] = product[13]; tmp[6] = product[14]; tmp[7] = product[15]; - carry = (int) uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s2 */ tmp[3] = product[12]; @@ -12461,8 +13147,8 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[5] = product[14]; tmp[6] = product[15]; tmp[7] = 0; - carry += (int) uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s3 */ tmp[0] = product[8]; @@ -12471,7 +13157,7 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[3] = tmp[4] = tmp[5] = 0; tmp[6] = product[14]; tmp[7] = product[15]; - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s4 */ tmp[0] = product[9]; @@ -12482,7 +13168,7 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[5] = product[15]; tmp[6] = product[13]; tmp[7] = product[8]; - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* d1 */ tmp[0] = product[11]; @@ -12491,7 +13177,7 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[3] = tmp[4] = tmp[5] = 0; tmp[6] = product[8]; tmp[7] = product[10]; - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d2 */ tmp[0] = product[12]; @@ -12501,7 +13187,7 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[4] = tmp[5] = 0; tmp[6] = product[9]; tmp[7] = product[11]; - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d3 */ tmp[0] = product[13]; @@ -12512,7 +13198,7 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[5] = product[10]; tmp[6] = 0; tmp[7] = product[12]; - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d4 */ tmp[0] = product[14]; @@ -12523,18 +13209,18 @@ static void vli_mmod_fast_secp256r1(uint32_t *result, uint32_t *product) { tmp[5] = product[11]; tmp[6] = 0; tmp[7] = product[13]; - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); if (carry < 0) { do { - carry += - (int) uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p, + num_words_secp256r1); } while (carry < 0); } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, - num_words_secp256r1) != 1) { - carry -= - (int) uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result, + num_words_secp256r1) != 1) { + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p, + num_words_secp256r1); } } } @@ -12544,94 +13230,95 @@ static void vli_mmod_fast_secp256r1(uint64_t *result, uint64_t *product) { int carry; /* t */ - uECC_vli_set(result, product, num_words_secp256r1); + mg_uecc_vli_set(result, product, num_words_secp256r1); /* s1 */ tmp[0] = 0; tmp[1] = product[5] & 0xffffffff00000000U; tmp[2] = product[6]; tmp[3] = product[7]; - carry = (int) uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry = (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s2 */ tmp[1] = product[6] << 32; tmp[2] = (product[6] >> 32) | (product[7] << 32); tmp[3] = product[7] >> 32; - carry += (int) uECC_vli_add(tmp, tmp, tmp, num_words_secp256r1); - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(tmp, tmp, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s3 */ tmp[0] = product[4]; tmp[1] = product[5] & 0xffffffff; tmp[2] = 0; tmp[3] = product[7]; - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* s4 */ tmp[0] = (product[4] >> 32) | (product[5] << 32); tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000U); tmp[2] = product[7]; tmp[3] = (product[6] >> 32) | (product[4] << 32); - carry += (int) uECC_vli_add(result, result, tmp, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, tmp, num_words_secp256r1); /* d1 */ tmp[0] = (product[5] >> 32) | (product[6] << 32); tmp[1] = (product[6] >> 32); tmp[2] = 0; tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d2 */ tmp[0] = product[6]; tmp[1] = product[7]; tmp[2] = 0; tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000); - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d3 */ tmp[0] = (product[6] >> 32) | (product[7] << 32); tmp[1] = (product[7] >> 32) | (product[4] << 32); tmp[2] = (product[4] >> 32) | (product[5] << 32); tmp[3] = (product[6] << 32); - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); /* d4 */ tmp[0] = product[7]; tmp[1] = product[4] & 0xffffffff00000000U; tmp[2] = product[5]; tmp[3] = product[6] & 0xffffffff00000000U; - carry -= (int) uECC_vli_sub(result, result, tmp, num_words_secp256r1); + carry -= (int) mg_uecc_vli_sub(result, result, tmp, num_words_secp256r1); if (carry < 0) { do { - carry += - (int) uECC_vli_add(result, result, curve_secp256r1.p, num_words_secp256r1); + carry += (int) mg_uecc_vli_add(result, result, curve_secp256r1.p, + num_words_secp256r1); } while (carry < 0); } else { - while (carry || uECC_vli_cmp_unsafe(curve_secp256r1.p, result, - num_words_secp256r1) != 1) { - carry -= - (int) uECC_vli_sub(result, result, curve_secp256r1.p, num_words_secp256r1); + while (carry || mg_uecc_vli_cmp_unsafe(curve_secp256r1.p, result, + num_words_secp256r1) != 1) { + carry -= (int) mg_uecc_vli_sub(result, result, curve_secp256r1.p, + num_words_secp256r1); } } } -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */ +#endif /* MG_UECC_WORD_SIZE */ +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256r1) */ -#endif /* uECC_SUPPORTS_secp256r1 */ +#endif /* MG_UECC_SUPPORTS_secp256r1 */ -#if uECC_SUPPORTS_secp256k1 +#if MG_UECC_SUPPORTS_secp256k1 -static void double_jacobian_secp256k1(uECC_word_t *X1, uECC_word_t *Y1, - uECC_word_t *Z1, uECC_Curve curve); -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, - uECC_Curve curve); -#if (uECC_OPTIMIZATION_LEVEL > 0) -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product); +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *Z1, MG_UECC_Curve curve); +static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x, + MG_UECC_Curve curve); +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result, + mg_uecc_word_t *product); #endif -static const struct uECC_Curve_t curve_secp256k1 = { +static const struct MG_UECC_Curve_t curve_secp256k1 = { num_words_secp256k1, num_bytes_secp256k1, 256, /* num_n_bits */ @@ -12657,101 +13344,109 @@ static const struct uECC_Curve_t curve_secp256k1 = { BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00), BYTES_TO_WORDS_8(00, 00, 00, 00, 00, 00, 00, 00)}, &double_jacobian_secp256k1, -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT &mod_sqrt_default, #endif &x_side_secp256k1, -#if (uECC_OPTIMIZATION_LEVEL > 0) +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) &vli_mmod_fast_secp256k1 #endif }; -uECC_Curve uECC_secp256k1(void) { +MG_UECC_Curve mg_uecc_secp256k1(void) { return &curve_secp256k1; } /* Double in place */ -static void double_jacobian_secp256k1(uECC_word_t *X1, uECC_word_t *Y1, - uECC_word_t *Z1, uECC_Curve curve) { +static void double_jacobian_secp256k1(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *Z1, MG_UECC_Curve curve) { /* t1 = X, t2 = Y, t3 = Z */ - uECC_word_t t4[num_words_secp256k1]; - uECC_word_t t5[num_words_secp256k1]; + mg_uecc_word_t t4[num_words_secp256k1]; + mg_uecc_word_t t5[num_words_secp256k1]; - if (uECC_vli_isZero(Z1, num_words_secp256k1)) { + if (mg_uecc_vli_isZero(Z1, num_words_secp256k1)) { return; } - uECC_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */ - uECC_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */ - uECC_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */ - uECC_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */ - - uECC_vli_modAdd(Y1, X1, X1, curve->p, num_words_secp256k1); /* t2 = 2*x1^2 */ - uECC_vli_modAdd(Y1, Y1, X1, curve->p, num_words_secp256k1); /* t2 = 3*x1^2 */ - if (uECC_vli_testBit(Y1, 0)) { - uECC_word_t carry = uECC_vli_add(Y1, Y1, curve->p, num_words_secp256k1); - uECC_vli_rshift1(Y1, num_words_secp256k1); - Y1[num_words_secp256k1 - 1] |= carry << (uECC_WORD_BITS - 1); + mg_uecc_vli_modSquare_fast(t5, Y1, curve); /* t5 = y1^2 */ + mg_uecc_vli_modMult_fast(t4, X1, t5, curve); /* t4 = x1*y1^2 = A */ + mg_uecc_vli_modSquare_fast(X1, X1, curve); /* t1 = x1^2 */ + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = y1^4 */ + mg_uecc_vli_modMult_fast(Z1, Y1, Z1, curve); /* t3 = y1*z1 = z3 */ + + mg_uecc_vli_modAdd(Y1, X1, X1, curve->p, + num_words_secp256k1); /* t2 = 2*x1^2 */ + mg_uecc_vli_modAdd(Y1, Y1, X1, curve->p, + num_words_secp256k1); /* t2 = 3*x1^2 */ + if (mg_uecc_vli_testBit(Y1, 0)) { + mg_uecc_word_t carry = + mg_uecc_vli_add(Y1, Y1, curve->p, num_words_secp256k1); + mg_uecc_vli_rshift1(Y1, num_words_secp256k1); + Y1[num_words_secp256k1 - 1] |= carry << (MG_UECC_WORD_BITS - 1); } else { - uECC_vli_rshift1(Y1, num_words_secp256k1); + mg_uecc_vli_rshift1(Y1, num_words_secp256k1); } /* t2 = 3/2*(x1^2) = B */ - uECC_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */ - uECC_vli_modSub(X1, X1, t4, curve->p, num_words_secp256k1); /* t1 = B^2 - A */ - uECC_vli_modSub(X1, X1, t4, curve->p, - num_words_secp256k1); /* t1 = B^2 - 2A = x3 */ + mg_uecc_vli_modSquare_fast(X1, Y1, curve); /* t1 = B^2 */ + mg_uecc_vli_modSub(X1, X1, t4, curve->p, + num_words_secp256k1); /* t1 = B^2 - A */ + mg_uecc_vli_modSub(X1, X1, t4, curve->p, + num_words_secp256k1); /* t1 = B^2 - 2A = x3 */ - uECC_vli_modSub(t4, t4, X1, curve->p, num_words_secp256k1); /* t4 = A - x3 */ - uECC_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */ - uECC_vli_modSub(Y1, Y1, t5, curve->p, - num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */ + mg_uecc_vli_modSub(t4, t4, X1, curve->p, + num_words_secp256k1); /* t4 = A - x3 */ + mg_uecc_vli_modMult_fast(Y1, Y1, t4, curve); /* t2 = B * (A - x3) */ + mg_uecc_vli_modSub(Y1, Y1, t5, curve->p, + num_words_secp256k1); /* t2 = B * (A - x3) - y1^4 = y3 */ } /* Computes result = x^3 + b. result must not overlap x. */ -static void x_side_secp256k1(uECC_word_t *result, const uECC_word_t *x, - uECC_Curve curve) { - uECC_vli_modSquare_fast(result, x, curve); /* r = x^2 */ - uECC_vli_modMult_fast(result, result, x, curve); /* r = x^3 */ - uECC_vli_modAdd(result, result, curve->b, curve->p, - num_words_secp256k1); /* r = x^3 + b */ +static void x_side_secp256k1(mg_uecc_word_t *result, const mg_uecc_word_t *x, + MG_UECC_Curve curve) { + mg_uecc_vli_modSquare_fast(result, x, curve); /* r = x^2 */ + mg_uecc_vli_modMult_fast(result, result, x, curve); /* r = x^3 */ + mg_uecc_vli_modAdd(result, result, curve->b, curve->p, + num_words_secp256k1); /* r = x^3 + b */ } -#if (uECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1) -static void omega_mult_secp256k1(uECC_word_t *result, const uECC_word_t *right); -static void vli_mmod_fast_secp256k1(uECC_word_t *result, uECC_word_t *product) { - uECC_word_t tmp[2 * num_words_secp256k1]; - uECC_word_t carry; +#if (MG_UECC_OPTIMIZATION_LEVEL > 0 && !asm_mmod_fast_secp256k1) +static void omega_mult_secp256k1(mg_uecc_word_t *result, + const mg_uecc_word_t *right); +static void vli_mmod_fast_secp256k1(mg_uecc_word_t *result, + mg_uecc_word_t *product) { + mg_uecc_word_t tmp[2 * num_words_secp256k1]; + mg_uecc_word_t carry; - uECC_vli_clear(tmp, num_words_secp256k1); - uECC_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1); + mg_uecc_vli_clear(tmp, num_words_secp256k1); + mg_uecc_vli_clear(tmp + num_words_secp256k1, num_words_secp256k1); omega_mult_secp256k1(tmp, product + num_words_secp256k1); /* (Rq, q) = q * c */ - carry = uECC_vli_add(result, product, tmp, - num_words_secp256k1); /* (C, r) = r + q */ - uECC_vli_clear(product, num_words_secp256k1); + carry = mg_uecc_vli_add(result, product, tmp, + num_words_secp256k1); /* (C, r) = r + q */ + mg_uecc_vli_clear(product, num_words_secp256k1); omega_mult_secp256k1(product, tmp + num_words_secp256k1); /* Rq*c */ - carry += uECC_vli_add(result, result, product, - num_words_secp256k1); /* (C1, r) = r + Rq*c */ + carry += mg_uecc_vli_add(result, result, product, + num_words_secp256k1); /* (C1, r) = r + Rq*c */ while (carry > 0) { --carry; - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); + mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); } - if (uECC_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) > 0) { - uECC_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); + if (mg_uecc_vli_cmp_unsafe(result, curve_secp256k1.p, num_words_secp256k1) > + 0) { + mg_uecc_vli_sub(result, result, curve_secp256k1.p, num_words_secp256k1); } } -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 static void omega_mult_secp256k1(uint8_t *result, const uint8_t *right) { /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; + mg_uecc_word_t r0 = 0; + mg_uecc_word_t r1 = 0; + mg_uecc_word_t r2 = 0; wordcount_t k; /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ @@ -12774,9 +13469,9 @@ static void omega_mult_secp256k1(uint8_t *result, const uint8_t *right) { result[num_words_secp256k1 + 1] = r1; /* add the 2^32 multiple */ result[4 + num_words_secp256k1] = - uECC_vli_add(result + 4, result + 4, right, num_words_secp256k1); + mg_uecc_vli_add(result + 4, result + 4, right, num_words_secp256k1); } -#elif uECC_WORD_SIZE == 4 +#elif MG_UECC_WORD_SIZE == 4 static void omega_mult_secp256k1(uint32_t *result, const uint32_t *right) { /* Multiply by (2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ uint32_t carry = 0; @@ -12790,13 +13485,13 @@ static void omega_mult_secp256k1(uint32_t *result, const uint32_t *right) { result[num_words_secp256k1] = carry; /* add the 2^32 multiple */ result[1 + num_words_secp256k1] = - uECC_vli_add(result + 1, result + 1, right, num_words_secp256k1); + mg_uecc_vli_add(result + 1, result + 1, right, num_words_secp256k1); } #else static void omega_mult_secp256k1(uint64_t *result, const uint64_t *right) { - uECC_word_t r0 = 0; - uECC_word_t r1 = 0; - uECC_word_t r2 = 0; + mg_uecc_word_t r0 = 0; + mg_uecc_word_t r1 = 0; + mg_uecc_word_t r2 = 0; wordcount_t k; /* Multiply by (2^32 + 2^9 + 2^8 + 2^7 + 2^6 + 2^4 + 1). */ @@ -12809,48 +13504,48 @@ static void omega_mult_secp256k1(uint64_t *result, const uint64_t *right) { } result[num_words_secp256k1] = r0; } -#endif /* uECC_WORD_SIZE */ -#endif /* (uECC_OPTIMIZATION_LEVEL > 0 && && !asm_mmod_fast_secp256k1) */ +#endif /* MG_UECC_WORD_SIZE */ +#endif /* (MG_UECC_OPTIMIZATION_LEVEL > 0 && && !asm_mmod_fast_secp256k1) */ -#endif /* uECC_SUPPORTS_secp256k1 */ +#endif /* MG_UECC_SUPPORTS_secp256k1 */ #endif /* _UECC_CURVE_SPECIFIC_H_ */ /* Returns 1 if 'point' is the point at infinity, 0 otherwise. */ #define EccPoint_isZero(point, curve) \ - uECC_vli_isZero((point), (wordcount_t) ((curve)->num_words * 2)) + mg_uecc_vli_isZero((point), (wordcount_t) ((curve)->num_words * 2)) /* Point multiplication algorithm using Montgomery's ladder with co-Z coordinates. From http://eprint.iacr.org/2011/338.pdf */ /* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ -static void apply_z(uECC_word_t *X1, uECC_word_t *Y1, - const uECC_word_t *const Z, uECC_Curve curve) { - uECC_word_t t1[uECC_MAX_WORDS]; +static void apply_z(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + const mg_uecc_word_t *const Z, MG_UECC_Curve curve) { + mg_uecc_word_t t1[MG_UECC_MAX_WORDS]; - uECC_vli_modSquare_fast(t1, Z, curve); /* z^2 */ - uECC_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */ - uECC_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */ - uECC_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */ + mg_uecc_vli_modSquare_fast(t1, Z, curve); /* z^2 */ + mg_uecc_vli_modMult_fast(X1, X1, t1, curve); /* x1 * z^2 */ + mg_uecc_vli_modMult_fast(t1, t1, Z, curve); /* z^3 */ + mg_uecc_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */ } /* P = (x1, y1) => 2P, (x2, y2) => P' */ -static void XYcZ_initial_double(uECC_word_t *X1, uECC_word_t *Y1, - uECC_word_t *X2, uECC_word_t *Y2, - const uECC_word_t *const initial_Z, - uECC_Curve curve) { - uECC_word_t z[uECC_MAX_WORDS]; +static void XYcZ_initial_double(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *X2, mg_uecc_word_t *Y2, + const mg_uecc_word_t *const initial_Z, + MG_UECC_Curve curve) { + mg_uecc_word_t z[MG_UECC_MAX_WORDS]; wordcount_t num_words = curve->num_words; if (initial_Z) { - uECC_vli_set(z, initial_Z, num_words); + mg_uecc_vli_set(z, initial_Z, num_words); } else { - uECC_vli_clear(z, num_words); + mg_uecc_vli_clear(z, num_words); z[0] = 1; } - uECC_vli_set(X2, X1, num_words); - uECC_vli_set(Y2, Y1, num_words); + mg_uecc_vli_set(X2, X1, num_words); + mg_uecc_vli_set(Y2, Y1, num_words); apply_z(X1, Y1, z, curve); curve->double_jacobian(X1, Y1, z, curve); @@ -12861,163 +13556,167 @@ static void XYcZ_initial_double(uECC_word_t *X1, uECC_word_t *Y1, Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) or P => P', Q => P + Q */ -static void XYcZ_add(uECC_word_t *X1, uECC_word_t *Y1, uECC_word_t *X2, - uECC_word_t *Y2, uECC_Curve curve) { +static void XYcZ_add(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, mg_uecc_word_t *X2, + mg_uecc_word_t *Y2, MG_UECC_Curve curve) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS] = {0}; + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0}; wordcount_t num_words = curve->num_words; - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - uECC_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */ + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ + mg_uecc_vli_modSquare_fast(t5, Y2, curve); /* t5 = (y2 - y1)^2 = D */ - uECC_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */ - uECC_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */ - uECC_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */ - uECC_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */ + mg_uecc_vli_modSub(t5, t5, X1, curve->p, num_words); /* t5 = D - B */ + mg_uecc_vli_modSub(t5, t5, X2, curve->p, num_words); /* t5 = D - B - C = x3 */ + mg_uecc_vli_modSub(X2, X2, X1, curve->p, num_words); /* t3 = C - B */ + mg_uecc_vli_modMult_fast(Y1, Y1, X2, curve); /* t2 = y1*(C - B) */ + mg_uecc_vli_modSub(X2, X1, t5, curve->p, num_words); /* t3 = B - x3 */ + mg_uecc_vli_modMult_fast(Y2, Y2, X2, curve); /* t4 = (y2 - y1)*(B - x3) */ + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y3 */ - uECC_vli_set(X2, t5, num_words); + mg_uecc_vli_set(X2, t5, num_words); } /* Input P = (x1, y1, Z), Q = (x2, y2, Z) Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) or P => P - Q, Q => P + Q */ -static void XYcZ_addC(uECC_word_t *X1, uECC_word_t *Y1, uECC_word_t *X2, - uECC_word_t *Y2, uECC_Curve curve) { +static void XYcZ_addC(mg_uecc_word_t *X1, mg_uecc_word_t *Y1, + mg_uecc_word_t *X2, mg_uecc_word_t *Y2, + MG_UECC_Curve curve) { /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - uECC_word_t t5[uECC_MAX_WORDS] = {0}; - uECC_word_t t6[uECC_MAX_WORDS]; - uECC_word_t t7[uECC_MAX_WORDS]; + mg_uecc_word_t t5[MG_UECC_MAX_WORDS] = {0}; + mg_uecc_word_t t6[MG_UECC_MAX_WORDS]; + mg_uecc_word_t t7[MG_UECC_MAX_WORDS]; wordcount_t num_words = curve->num_words; - uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ - uECC_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ - uECC_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ - uECC_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ - uECC_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ + mg_uecc_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ + mg_uecc_vli_modSquare_fast(t5, t5, curve); /* t5 = (x2 - x1)^2 = A */ + mg_uecc_vli_modMult_fast(X1, X1, t5, curve); /* t1 = x1*A = B */ + mg_uecc_vli_modMult_fast(X2, X2, t5, curve); /* t3 = x2*A = C */ + mg_uecc_vli_modAdd(t5, Y2, Y1, curve->p, num_words); /* t5 = y2 + y1 */ + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, num_words); /* t4 = y2 - y1 */ - uECC_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */ - uECC_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */ - uECC_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */ - uECC_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */ - uECC_vli_modSub(X2, X2, t6, curve->p, num_words); /* t3 = D - (B + C) = x3 */ + mg_uecc_vli_modSub(t6, X2, X1, curve->p, num_words); /* t6 = C - B */ + mg_uecc_vli_modMult_fast(Y1, Y1, t6, curve); /* t2 = y1 * (C - B) = E */ + mg_uecc_vli_modAdd(t6, X1, X2, curve->p, num_words); /* t6 = B + C */ + mg_uecc_vli_modSquare_fast(X2, Y2, curve); /* t3 = (y2 - y1)^2 = D */ + mg_uecc_vli_modSub(X2, X2, t6, curve->p, + num_words); /* t3 = D - (B + C) = x3 */ - uECC_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */ - uECC_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */ - uECC_vli_modSub(Y2, Y2, Y1, curve->p, - num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */ + mg_uecc_vli_modSub(t7, X1, X2, curve->p, num_words); /* t7 = B - x3 */ + mg_uecc_vli_modMult_fast(Y2, Y2, t7, curve); /* t4 = (y2 - y1)*(B - x3) */ + mg_uecc_vli_modSub(Y2, Y2, Y1, curve->p, + num_words); /* t4 = (y2 - y1)*(B - x3) - E = y3 */ - uECC_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */ - uECC_vli_modSub(t7, t7, t6, curve->p, num_words); /* t7 = F - (B + C) = x3' */ - uECC_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */ - uECC_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */ - uECC_vli_modSub(Y1, t6, Y1, curve->p, - num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */ + mg_uecc_vli_modSquare_fast(t7, t5, curve); /* t7 = (y2 + y1)^2 = F */ + mg_uecc_vli_modSub(t7, t7, t6, curve->p, + num_words); /* t7 = F - (B + C) = x3' */ + mg_uecc_vli_modSub(t6, t7, X1, curve->p, num_words); /* t6 = x3' - B */ + mg_uecc_vli_modMult_fast(t6, t6, t5, curve); /* t6 = (y2+y1)*(x3' - B) */ + mg_uecc_vli_modSub(Y1, t6, Y1, curve->p, + num_words); /* t2 = (y2+y1)*(x3' - B) - E = y3' */ - uECC_vli_set(X1, t7, num_words); + mg_uecc_vli_set(X1, t7, num_words); } /* result may overlap point. */ -static void EccPoint_mult(uECC_word_t *result, const uECC_word_t *point, - const uECC_word_t *scalar, - const uECC_word_t *initial_Z, bitcount_t num_bits, - uECC_Curve curve) { +static void EccPoint_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point, + const mg_uecc_word_t *scalar, + const mg_uecc_word_t *initial_Z, bitcount_t num_bits, + MG_UECC_Curve curve) { /* R0 and R1 */ - uECC_word_t Rx[2][uECC_MAX_WORDS]; - uECC_word_t Ry[2][uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; + mg_uecc_word_t Rx[2][MG_UECC_MAX_WORDS]; + mg_uecc_word_t Ry[2][MG_UECC_MAX_WORDS]; + mg_uecc_word_t z[MG_UECC_MAX_WORDS]; bitcount_t i; - uECC_word_t nb; + mg_uecc_word_t nb; wordcount_t num_words = curve->num_words; - uECC_vli_set(Rx[1], point, num_words); - uECC_vli_set(Ry[1], point + num_words, num_words); + mg_uecc_vli_set(Rx[1], point, num_words); + mg_uecc_vli_set(Ry[1], point + num_words, num_words); XYcZ_initial_double(Rx[1], Ry[1], Rx[0], Ry[0], initial_Z, curve); for (i = num_bits - 2; i > 0; --i) { - nb = !uECC_vli_testBit(scalar, i); + nb = !mg_uecc_vli_testBit(scalar, i); XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); } - nb = !uECC_vli_testBit(scalar, 0); + nb = !mg_uecc_vli_testBit(scalar, 0); XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve); /* Find final 1/Z value. */ - uECC_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */ - uECC_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1 - X0) */ - uECC_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1 - X0) */ - uECC_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */ + mg_uecc_vli_modSub(z, Rx[1], Rx[0], curve->p, num_words); /* X1 - X0 */ + mg_uecc_vli_modMult_fast(z, z, Ry[1 - nb], curve); /* Yb * (X1 - X0) */ + mg_uecc_vli_modMult_fast(z, z, point, curve); /* xP * Yb * (X1 - X0) */ + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* 1 / (xP * Yb * (X1 - X0)) */ /* yP / (xP * Yb * (X1 - X0)) */ - uECC_vli_modMult_fast(z, z, point + num_words, curve); - uECC_vli_modMult_fast(z, z, Rx[1 - nb], - curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */ + mg_uecc_vli_modMult_fast(z, z, point + num_words, curve); + mg_uecc_vli_modMult_fast(z, z, Rx[1 - nb], + curve); /* Xb * yP / (xP * Yb * (X1 - X0)) */ /* End 1/Z calculation */ XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve); apply_z(Rx[0], Ry[0], z, curve); - uECC_vli_set(result, Rx[0], num_words); - uECC_vli_set(result + num_words, Ry[0], num_words); + mg_uecc_vli_set(result, Rx[0], num_words); + mg_uecc_vli_set(result + num_words, Ry[0], num_words); } -static uECC_word_t regularize_k(const uECC_word_t *const k, uECC_word_t *k0, - uECC_word_t *k1, uECC_Curve curve) { +static mg_uecc_word_t regularize_k(const mg_uecc_word_t *const k, + mg_uecc_word_t *k0, mg_uecc_word_t *k1, + MG_UECC_Curve curve) { wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t carry = - uECC_vli_add(k0, k, curve->n, num_n_words) || - (num_n_bits < ((bitcount_t) num_n_words * uECC_WORD_SIZE * 8) && - uECC_vli_testBit(k0, num_n_bits)); - uECC_vli_add(k1, k0, curve->n, num_n_words); + mg_uecc_word_t carry = + mg_uecc_vli_add(k0, k, curve->n, num_n_words) || + (num_n_bits < ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8) && + mg_uecc_vli_testBit(k0, num_n_bits)); + mg_uecc_vli_add(k1, k0, curve->n, num_n_words); return carry; } /* Generates a random integer in the range 0 < random < top. Both random and top have num_words words. */ -uECC_VLI_API int uECC_generate_random_int(uECC_word_t *random, - const uECC_word_t *top, - wordcount_t num_words) { - uECC_word_t mask = (uECC_word_t) -1; - uECC_word_t tries; - bitcount_t num_bits = uECC_vli_numBits(top, num_words); +MG_UECC_VLI_API int mg_uecc_generate_random_int(mg_uecc_word_t *random, + const mg_uecc_word_t *top, + wordcount_t num_words) { + mg_uecc_word_t mask = (mg_uecc_word_t) -1; + mg_uecc_word_t tries; + bitcount_t num_bits = mg_uecc_vli_numBits(top, num_words); if (!g_rng_function) { return 0; } - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) { if (!g_rng_function((uint8_t *) random, - (unsigned int) (num_words * uECC_WORD_SIZE))) { + (unsigned int) (num_words * MG_UECC_WORD_SIZE))) { return 0; } random[num_words - 1] &= - mask >> ((bitcount_t) (num_words * uECC_WORD_SIZE * 8 - num_bits)); - if (!uECC_vli_isZero(random, num_words) && - uECC_vli_cmp(top, random, num_words) == 1) { + mask >> ((bitcount_t) (num_words * MG_UECC_WORD_SIZE * 8 - num_bits)); + if (!mg_uecc_vli_isZero(random, num_words) && + mg_uecc_vli_cmp(top, random, num_words) == 1) { return 1; } } return 0; } -static uECC_word_t EccPoint_compute_public_key(uECC_word_t *result, - uECC_word_t *private_key, - uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; +static mg_uecc_word_t EccPoint_compute_public_key(mg_uecc_word_t *result, + mg_uecc_word_t *private_key, + MG_UECC_Curve curve) { + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS]; + mg_uecc_word_t *p2[2] = {tmp1, tmp2}; + mg_uecc_word_t *initial_Z = 0; + mg_uecc_word_t carry; /* Regularize the bitcount for the private key so that attackers cannot use a side channel attack to learn the number of leading zeros. */ @@ -13026,7 +13725,7 @@ static uECC_word_t EccPoint_compute_public_key(uECC_word_t *result, /* If an RNG function was specified, try to get a random initial Z value to improve protection against side-channel attacks. */ if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, curve->num_words)) { + if (!mg_uecc_generate_random_int(p2[carry], curve->p, curve->num_words)) { return 0; } initial_Z = p2[carry]; @@ -13040,70 +13739,74 @@ static uECC_word_t EccPoint_compute_public_key(uECC_word_t *result, return 1; } -#if uECC_WORD_SIZE == 1 +#if MG_UECC_WORD_SIZE == 1 -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, - const uint8_t *native) { +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes, + const uint8_t *native) { wordcount_t i; for (i = 0; i < num_bytes; ++i) { bytes[i] = native[(num_bytes - 1) - i]; } } -uECC_VLI_API void uECC_vli_bytesToNative(uint8_t *native, const uint8_t *bytes, - int num_bytes) { - uECC_vli_nativeToBytes(native, num_bytes, bytes); +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(uint8_t *native, + const uint8_t *bytes, + int num_bytes) { + mg_uecc_vli_nativeToBytes(native, num_bytes, bytes); } #else -uECC_VLI_API void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, - const uECC_word_t *native) { +MG_UECC_VLI_API void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes, + const mg_uecc_word_t *native) { int i; for (i = 0; i < num_bytes; ++i) { unsigned b = (unsigned) (num_bytes - 1 - i); - bytes[i] = - (uint8_t) (native[b / uECC_WORD_SIZE] >> (8 * (b % uECC_WORD_SIZE))); + bytes[i] = (uint8_t) (native[b / MG_UECC_WORD_SIZE] >> + (8 * (b % MG_UECC_WORD_SIZE))); } } -uECC_VLI_API void uECC_vli_bytesToNative(uECC_word_t *native, - const uint8_t *bytes, int num_bytes) { +MG_UECC_VLI_API void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, + const uint8_t *bytes, + int num_bytes) { int i; - uECC_vli_clear(native, (wordcount_t) ((num_bytes + (uECC_WORD_SIZE - 1)) / - uECC_WORD_SIZE)); + mg_uecc_vli_clear(native, + (wordcount_t) ((num_bytes + (MG_UECC_WORD_SIZE - 1)) / + MG_UECC_WORD_SIZE)); for (i = 0; i < num_bytes; ++i) { unsigned b = (unsigned) (num_bytes - 1 - i); - native[b / uECC_WORD_SIZE] |= (uECC_word_t) bytes[i] - << (8 * (b % uECC_WORD_SIZE)); + native[b / MG_UECC_WORD_SIZE] |= (mg_uecc_word_t) bytes[i] + << (8 * (b % MG_UECC_WORD_SIZE)); } } -#endif /* uECC_WORD_SIZE */ +#endif /* MG_UECC_WORD_SIZE */ -int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *) private_key; - uECC_word_t *_public = (uECC_word_t *) public_key; +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key, + MG_UECC_Curve curve) { +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key; + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key; #else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; + mg_uecc_word_t _private[MG_UECC_MAX_WORDS]; + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2]; #endif - uECC_word_t tries; + mg_uecc_word_t tries; - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(_private, curve->n, - BITS_TO_WORDS(curve->num_n_bits))) { + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) { + if (!mg_uecc_generate_random_int(_private, curve->n, + BITS_TO_WORDS(curve->num_n_bits))) { return 0; } if (EccPoint_compute_public_key(_public, _private, curve)) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits), - _private); - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, - _public + curve->num_words); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_nativeToBytes(private_key, BITS_TO_BYTES(curve->num_n_bits), + _private); + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public); + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, + _public + curve->num_words); #endif return 1; } @@ -13111,27 +13814,27 @@ int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve) { return 0; } -int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key, - uint8_t *secret, uECC_Curve curve) { - uECC_word_t _public[uECC_MAX_WORDS * 2]; - uECC_word_t _private[uECC_MAX_WORDS]; +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key, + uint8_t *secret, MG_UECC_Curve curve) { + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2]; + mg_uecc_word_t _private[MG_UECC_MAX_WORDS]; - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {_private, tmp}; - uECC_word_t *initial_Z = 0; - uECC_word_t carry; + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS]; + mg_uecc_word_t *p2[2] = {_private, tmp}; + mg_uecc_word_t *initial_Z = 0; + mg_uecc_word_t carry; wordcount_t num_words = curve->num_words; wordcount_t num_bytes = curve->num_bytes; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) _private, private_key, num_bytes); bcopy((uint8_t *) _public, public_key, num_bytes * 2); #else - uECC_vli_bytesToNative(_private, private_key, - BITS_TO_BYTES(curve->num_n_bits)); - uECC_vli_bytesToNative(_public, public_key, num_bytes); - uECC_vli_bytesToNative(_public + num_words, public_key + num_bytes, - num_bytes); + mg_uecc_vli_bytesToNative(_private, private_key, + BITS_TO_BYTES(curve->num_n_bits)); + mg_uecc_vli_bytesToNative(_public, public_key, num_bytes); + mg_uecc_vli_bytesToNative(_public + num_words, public_key + num_bytes, + num_bytes); #endif /* Regularize the bitcount for the private key so that attackers cannot use a @@ -13141,7 +13844,7 @@ int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key, /* If an RNG function was specified, try to get a random initial Z value to improve protection against side-channel attacks. */ if (g_rng_function) { - if (!uECC_generate_random_int(p2[carry], curve->p, num_words)) { + if (!mg_uecc_generate_random_int(p2[carry], curve->p, num_words)) { return 0; } initial_Z = p2[carry]; @@ -13149,58 +13852,59 @@ int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key, EccPoint_mult(_public, _public, p2[!carry], initial_Z, (bitcount_t) (curve->num_n_bits + 1), curve); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) secret, (uint8_t *) _public, num_bytes); #else - uECC_vli_nativeToBytes(secret, num_bytes, _public); + mg_uecc_vli_nativeToBytes(secret, num_bytes, _public); #endif return !EccPoint_isZero(_public, curve); } -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, - uECC_Curve curve) { +#if MG_UECC_SUPPORT_COMPRESSED_POINT +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed, + MG_UECC_Curve curve) { wordcount_t i; for (i = 0; i < curve->num_bytes; ++i) { compressed[i + 1] = public_key[i]; } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN compressed[0] = 2 + (public_key[curve->num_bytes] & 0x01); #else compressed[0] = 2 + (public_key[curve->num_bytes * 2 - 1] & 0x01); #endif } -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, - uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *point = (uECC_word_t *) public_key; +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key, + MG_UECC_Curve curve) { +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *point = (mg_uecc_word_t *) public_key; #else - uECC_word_t point[uECC_MAX_WORDS * 2]; + mg_uecc_word_t point[MG_UECC_MAX_WORDS * 2]; #endif - uECC_word_t *y = point + curve->num_words; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *y = point + curve->num_words; +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy(public_key, compressed + 1, curve->num_bytes); #else - uECC_vli_bytesToNative(point, compressed + 1, curve->num_bytes); + mg_uecc_vli_bytesToNative(point, compressed + 1, curve->num_bytes); #endif curve->x_side(y, point, curve); curve->mod_sqrt(y, curve); if ((uint8_t) (y[0] & 0x01) != (compressed[0] & 0x01)) { - uECC_vli_sub(y, curve->p, y, curve->num_words); + mg_uecc_vli_sub(y, curve->p, y, curve->num_words); } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, point); - uECC_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, point); + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, y); #endif } -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */ -uECC_VLI_API int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; +MG_UECC_VLI_API int mg_uecc_valid_point(const mg_uecc_word_t *point, + MG_UECC_Curve curve) { + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS]; wordcount_t num_words = curve->num_words; /* The point at infinity is invalid. */ @@ -13209,54 +13913,55 @@ uECC_VLI_API int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve) { } /* x and y must be smaller than p. */ - if (uECC_vli_cmp_unsafe(curve->p, point, num_words) != 1 || - uECC_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) { + if (mg_uecc_vli_cmp_unsafe(curve->p, point, num_words) != 1 || + mg_uecc_vli_cmp_unsafe(curve->p, point + num_words, num_words) != 1) { return 0; } - uECC_vli_modSquare_fast(tmp1, point + num_words, curve); + mg_uecc_vli_modSquare_fast(tmp1, point + num_words, curve); curve->x_side(tmp2, point, curve); /* tmp2 = x^3 + ax + b */ /* Make sure that y^2 == x^3 + ax + b */ - return (int) (uECC_vli_equal(tmp1, tmp2, num_words)); + return (int) (mg_uecc_vli_equal(tmp1, tmp2, num_words)); } -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *) public_key; +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve) { +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key; #else - uECC_word_t _public[uECC_MAX_WORDS * 2]; + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2]; #endif -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative(_public + curve->num_words, - public_key + curve->num_bytes, curve->num_bytes); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes); + mg_uecc_vli_bytesToNative(_public + curve->num_words, + public_key + curve->num_bytes, curve->num_bytes); #endif - return uECC_valid_point(_public, curve); + return mg_uecc_valid_point(_public, curve); } -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, - uECC_Curve curve) { -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_private = (uECC_word_t *) private_key; - uECC_word_t *_public = (uECC_word_t *) public_key; +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key, + MG_UECC_Curve curve) { +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *_private = (mg_uecc_word_t *) private_key; + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key; #else - uECC_word_t _private[uECC_MAX_WORDS]; - uECC_word_t _public[uECC_MAX_WORDS * 2]; + mg_uecc_word_t _private[MG_UECC_MAX_WORDS]; + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2]; #endif -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_bytesToNative(_private, private_key, - BITS_TO_BYTES(curve->num_n_bits)); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_bytesToNative(_private, private_key, + BITS_TO_BYTES(curve->num_n_bits)); #endif /* Make sure the private key is in the range [1, n-1]. */ - if (uECC_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) { + if (mg_uecc_vli_isZero(_private, BITS_TO_WORDS(curve->num_n_bits))) { return 0; } - if (uECC_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) != 1) { + if (mg_uecc_vli_cmp(curve->n, _private, BITS_TO_WORDS(curve->num_n_bits)) != + 1) { return 0; } @@ -13265,33 +13970,33 @@ int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, return 0; } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(public_key, curve->num_bytes, _public); - uECC_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, - _public + curve->num_words); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_nativeToBytes(public_key, curve->num_bytes, _public); + mg_uecc_vli_nativeToBytes(public_key + curve->num_bytes, curve->num_bytes, + _public + curve->num_words); #endif return 1; } /* -------- ECDSA code -------- */ -static void bits2int(uECC_word_t *native, const uint8_t *bits, - unsigned bits_size, uECC_Curve curve) { +static void bits2int(mg_uecc_word_t *native, const uint8_t *bits, + unsigned bits_size, MG_UECC_Curve curve) { unsigned num_n_bytes = (unsigned) BITS_TO_BYTES(curve->num_n_bits); unsigned num_n_words = (unsigned) BITS_TO_WORDS(curve->num_n_bits); int shift; - uECC_word_t carry; - uECC_word_t *ptr; + mg_uecc_word_t carry; + mg_uecc_word_t *ptr; if (bits_size > num_n_bytes) { bits_size = num_n_bytes; } - uECC_vli_clear(native, (wordcount_t) num_n_words); -#if uECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_vli_clear(native, (wordcount_t) num_n_words); +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) native, bits, bits_size); #else - uECC_vli_bytesToNative(native, bits, (int) bits_size); + mg_uecc_vli_bytesToNative(native, bits, (int) bits_size); #endif if (bits_size * 8 <= (unsigned) curve->num_n_bits) { return; @@ -13300,38 +14005,40 @@ static void bits2int(uECC_word_t *native, const uint8_t *bits, carry = 0; ptr = native + num_n_words; while (ptr-- > native) { - uECC_word_t temp = *ptr; + mg_uecc_word_t temp = *ptr; *ptr = (temp >> shift) | carry; - carry = temp << (uECC_WORD_BITS - shift); + carry = temp << (MG_UECC_WORD_BITS - shift); } /* Reduce mod curve_n */ - if (uECC_vli_cmp_unsafe(curve->n, native, (wordcount_t) num_n_words) != 1) { - uECC_vli_sub(native, native, curve->n, (wordcount_t) num_n_words); - } -} - -static int uECC_sign_with_k_internal(const uint8_t *private_key, - const uint8_t *message_hash, - unsigned hash_size, uECC_word_t *k, - uint8_t *signature, uECC_Curve curve) { - uECC_word_t tmp[uECC_MAX_WORDS]; - uECC_word_t s[uECC_MAX_WORDS]; - uECC_word_t *k2[2] = {tmp, s}; - uECC_word_t *initial_Z = 0; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *p = (uECC_word_t *) signature; + if (mg_uecc_vli_cmp_unsafe(curve->n, native, (wordcount_t) num_n_words) != + 1) { + mg_uecc_vli_sub(native, native, curve->n, (wordcount_t) num_n_words); + } +} + +static int mg_uecc_sign_with_k_internal(const uint8_t *private_key, + const uint8_t *message_hash, + unsigned hash_size, mg_uecc_word_t *k, + uint8_t *signature, + MG_UECC_Curve curve) { + mg_uecc_word_t tmp[MG_UECC_MAX_WORDS]; + mg_uecc_word_t s[MG_UECC_MAX_WORDS]; + mg_uecc_word_t *k2[2] = {tmp, s}; + mg_uecc_word_t *initial_Z = 0; +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *p = (mg_uecc_word_t *) signature; #else - uECC_word_t p[uECC_MAX_WORDS * 2]; + mg_uecc_word_t p[MG_UECC_MAX_WORDS * 2]; #endif - uECC_word_t carry; + mg_uecc_word_t carry; wordcount_t num_words = curve->num_words; wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); bitcount_t num_n_bits = curve->num_n_bits; /* Make sure 0 < k < curve_n */ - if (uECC_vli_isZero(k, num_words) || - uECC_vli_cmp(curve->n, k, num_n_words) != 1) { + if (mg_uecc_vli_isZero(k, num_words) || + mg_uecc_vli_cmp(curve->n, k, num_n_words) != 1) { return 0; } @@ -13339,87 +14046,87 @@ static int uECC_sign_with_k_internal(const uint8_t *private_key, /* If an RNG function was specified, try to get a random initial Z value to improve protection against side-channel attacks. */ if (g_rng_function) { - if (!uECC_generate_random_int(k2[carry], curve->p, num_words)) { + if (!mg_uecc_generate_random_int(k2[carry], curve->p, num_words)) { return 0; } initial_Z = k2[carry]; } EccPoint_mult(p, curve->G, k2[!carry], initial_Z, (bitcount_t) (num_n_bits + 1), curve); - if (uECC_vli_isZero(p, num_words)) { + if (mg_uecc_vli_isZero(p, num_words)) { return 0; } /* If an RNG function was specified, get a random number to prevent side channel analysis of k. */ if (!g_rng_function) { - uECC_vli_clear(tmp, num_n_words); + mg_uecc_vli_clear(tmp, num_n_words); tmp[0] = 1; - } else if (!uECC_generate_random_int(tmp, curve->n, num_n_words)) { + } else if (!mg_uecc_generate_random_int(tmp, curve->n, num_n_words)) { return 0; } - /* Prevent side channel analysis of uECC_vli_modInv() to determine + /* Prevent side channel analysis of mg_uecc_vli_modInv() to determine bits of k / the private key by premultiplying by a random number */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */ - uECC_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */ - uECC_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */ + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k' = rand * k */ + mg_uecc_vli_modInv(k, k, curve->n, num_n_words); /* k = 1 / k' */ + mg_uecc_vli_modMult(k, k, tmp, curve->n, num_n_words); /* k = 1 / k */ -#if uECC_VLI_NATIVE_LITTLE_ENDIAN == 0 - uECC_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */ +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN == 0 + mg_uecc_vli_nativeToBytes(signature, curve->num_bytes, p); /* store r */ #endif -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) tmp, private_key, BITS_TO_BYTES(curve->num_n_bits)); #else - uECC_vli_bytesToNative(tmp, private_key, - BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */ + mg_uecc_vli_bytesToNative(tmp, private_key, + BITS_TO_BYTES(curve->num_n_bits)); /* tmp = d */ #endif s[num_n_words - 1] = 0; - uECC_vli_set(s, p, num_words); - uECC_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */ + mg_uecc_vli_set(s, p, num_words); + mg_uecc_vli_modMult(s, tmp, s, curve->n, num_n_words); /* s = r*d */ bits2int(tmp, message_hash, hash_size, curve); - uECC_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */ - uECC_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */ - if (uECC_vli_numBits(s, num_n_words) > (bitcount_t) curve->num_bytes * 8) { + mg_uecc_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */ + mg_uecc_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */ + if (mg_uecc_vli_numBits(s, num_n_words) > (bitcount_t) curve->num_bytes * 8) { return 0; } -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) signature + curve->num_bytes, (uint8_t *) s, curve->num_bytes); #else - uECC_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s); + mg_uecc_vli_nativeToBytes(signature + curve->num_bytes, curve->num_bytes, s); #endif return 1; } #if 0 /* For testing - sign with an explicitly specified k value */ -int uECC_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash, +int mg_uecc_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash, unsigned hash_size, const uint8_t *k, uint8_t *signature, - uECC_Curve curve) { - uECC_word_t k2[uECC_MAX_WORDS]; + MG_UECC_Curve curve) { + mg_uecc_word_t k2[MG_UECC_MAX_WORDS]; bits2int(k2, k, (unsigned) BITS_TO_BYTES(curve->num_n_bits), curve); - return uECC_sign_with_k_internal(private_key, message_hash, hash_size, k2, + return mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k2, signature, curve); } #endif -int uECC_sign(const uint8_t *private_key, const uint8_t *message_hash, - unsigned hash_size, uint8_t *signature, uECC_Curve curve) { - uECC_word_t k[uECC_MAX_WORDS]; - uECC_word_t tries; +int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash, + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve) { + mg_uecc_word_t k[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tries; - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - if (!uECC_generate_random_int(k, curve->n, - BITS_TO_WORDS(curve->num_n_bits))) { + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) { + if (!mg_uecc_generate_random_int(k, curve->n, + BITS_TO_WORDS(curve->num_n_bits))) { return 0; } - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, k, - signature, curve)) { + if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, k, + signature, curve)) { return 1; } } @@ -13428,7 +14135,8 @@ int uECC_sign(const uint8_t *private_key, const uint8_t *message_hash, /* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always the same size as the hash result size. */ -static void HMAC_init(const uECC_HashContext *hash_context, const uint8_t *K) { +static void HMAC_init(const MG_UECC_HashContext *hash_context, + const uint8_t *K) { uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; unsigned i; for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x36; @@ -13438,13 +14146,13 @@ static void HMAC_init(const uECC_HashContext *hash_context, const uint8_t *K) { hash_context->update_hash(hash_context, pad, hash_context->block_size); } -static void HMAC_update(const uECC_HashContext *hash_context, +static void HMAC_update(const MG_UECC_HashContext *hash_context, const uint8_t *message, unsigned message_size) { hash_context->update_hash(hash_context, message, message_size); } -static void HMAC_finish(const uECC_HashContext *hash_context, const uint8_t *K, - uint8_t *result) { +static void HMAC_finish(const MG_UECC_HashContext *hash_context, + const uint8_t *K, uint8_t *result) { uint8_t *pad = hash_context->tmp + 2 * hash_context->result_size; unsigned i; for (i = 0; i < hash_context->result_size; ++i) pad[i] = K[i] ^ 0x5c; @@ -13459,7 +14167,7 @@ static void HMAC_finish(const uECC_HashContext *hash_context, const uint8_t *K, } /* V = HMAC_K(V) */ -static void update_V(const uECC_HashContext *hash_context, uint8_t *K, +static void update_V(const MG_UECC_HashContext *hash_context, uint8_t *K, uint8_t *V) { HMAC_init(hash_context, K); HMAC_update(hash_context, V, hash_context->result_size); @@ -13474,16 +14182,16 @@ static void update_V(const uECC_HashContext *hash_context, uint8_t *K, Layout of hash_context->tmp: | | (1 byte overlapped 0x00 or 0x01) / */ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, uECC_Curve curve) { +int mg_uecc_sign_deterministic(const uint8_t *private_key, + const uint8_t *message_hash, unsigned hash_size, + const MG_UECC_HashContext *hash_context, + uint8_t *signature, MG_UECC_Curve curve) { uint8_t *K = hash_context->tmp; uint8_t *V = K + hash_context->result_size; wordcount_t num_bytes = curve->num_bytes; wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); bitcount_t num_n_bits = curve->num_n_bits; - uECC_word_t tries; + mg_uecc_word_t tries; unsigned i; for (i = 0; i < hash_context->result_size; ++i) { V[i] = 0x01; @@ -13510,29 +14218,29 @@ int uECC_sign_deterministic(const uint8_t *private_key, update_V(hash_context, K, V); - for (tries = 0; tries < uECC_RNG_MAX_TRIES; ++tries) { - uECC_word_t T[uECC_MAX_WORDS]; + for (tries = 0; tries < MG_UECC_RNG_MAX_TRIES; ++tries) { + mg_uecc_word_t T[MG_UECC_MAX_WORDS]; uint8_t *T_ptr = (uint8_t *) T; wordcount_t T_bytes = 0; for (;;) { update_V(hash_context, K, V); for (i = 0; i < hash_context->result_size; ++i) { T_ptr[T_bytes++] = V[i]; - if (T_bytes >= num_n_words * uECC_WORD_SIZE) { + if (T_bytes >= num_n_words * MG_UECC_WORD_SIZE) { goto filled; } } } filled: - if ((bitcount_t) num_n_words * uECC_WORD_SIZE * 8 > num_n_bits) { - uECC_word_t mask = (uECC_word_t) -1; + if ((bitcount_t) num_n_words * MG_UECC_WORD_SIZE * 8 > num_n_bits) { + mg_uecc_word_t mask = (mg_uecc_word_t) -1; T[num_n_words - 1] &= mask >> - ((bitcount_t) (num_n_words * uECC_WORD_SIZE * 8 - num_n_bits)); + ((bitcount_t) (num_n_words * MG_UECC_WORD_SIZE * 8 - num_n_bits)); } - if (uECC_sign_with_k_internal(private_key, message_hash, hash_size, T, - signature, curve)) { + if (mg_uecc_sign_with_k_internal(private_key, message_hash, hash_size, T, + signature, curve)) { return 1; } @@ -13551,27 +14259,27 @@ static bitcount_t smax(bitcount_t a, bitcount_t b) { return (a > b ? a : b); } -int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, - unsigned hash_size, const uint8_t *signature, - uECC_Curve curve) { - uECC_word_t u1[uECC_MAX_WORDS], u2[uECC_MAX_WORDS]; - uECC_word_t z[uECC_MAX_WORDS]; - uECC_word_t sum[uECC_MAX_WORDS * 2]; - uECC_word_t rx[uECC_MAX_WORDS]; - uECC_word_t ry[uECC_MAX_WORDS]; - uECC_word_t tx[uECC_MAX_WORDS]; - uECC_word_t ty[uECC_MAX_WORDS]; - uECC_word_t tz[uECC_MAX_WORDS]; - const uECC_word_t *points[4]; - const uECC_word_t *point; +int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash, + unsigned hash_size, const uint8_t *signature, + MG_UECC_Curve curve) { + mg_uecc_word_t u1[MG_UECC_MAX_WORDS], u2[MG_UECC_MAX_WORDS]; + mg_uecc_word_t z[MG_UECC_MAX_WORDS]; + mg_uecc_word_t sum[MG_UECC_MAX_WORDS * 2]; + mg_uecc_word_t rx[MG_UECC_MAX_WORDS]; + mg_uecc_word_t ry[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tx[MG_UECC_MAX_WORDS]; + mg_uecc_word_t ty[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tz[MG_UECC_MAX_WORDS]; + const mg_uecc_word_t *points[4]; + const mg_uecc_word_t *point; bitcount_t num_bits; bitcount_t i; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN - uECC_word_t *_public = (uECC_word_t *) public_key; +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN + mg_uecc_word_t *_public = (mg_uecc_word_t *) public_key; #else - uECC_word_t _public[uECC_MAX_WORDS * 2]; + mg_uecc_word_t _public[MG_UECC_MAX_WORDS * 2]; #endif - uECC_word_t r[uECC_MAX_WORDS], s[uECC_MAX_WORDS]; + mg_uecc_word_t r[MG_UECC_MAX_WORDS], s[MG_UECC_MAX_WORDS]; wordcount_t num_words = curve->num_words; wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); @@ -13579,43 +14287,43 @@ int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, r[num_n_words - 1] = 0; s[num_n_words - 1] = 0; -#if uECC_VLI_NATIVE_LITTLE_ENDIAN +#if MG_UECC_VLI_NATIVE_LITTLE_ENDIAN bcopy((uint8_t *) r, signature, curve->num_bytes); bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes); #else - uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); - uECC_vli_bytesToNative(_public + num_words, public_key + curve->num_bytes, - curve->num_bytes); - uECC_vli_bytesToNative(r, signature, curve->num_bytes); - uECC_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); + mg_uecc_vli_bytesToNative(_public, public_key, curve->num_bytes); + mg_uecc_vli_bytesToNative(_public + num_words, public_key + curve->num_bytes, + curve->num_bytes); + mg_uecc_vli_bytesToNative(r, signature, curve->num_bytes); + mg_uecc_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); #endif /* r, s must not be 0. */ - if (uECC_vli_isZero(r, num_words) || uECC_vli_isZero(s, num_words)) { + if (mg_uecc_vli_isZero(r, num_words) || mg_uecc_vli_isZero(s, num_words)) { return 0; } /* r, s must be < n. */ - if (uECC_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 || - uECC_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) { + if (mg_uecc_vli_cmp_unsafe(curve->n, r, num_n_words) != 1 || + mg_uecc_vli_cmp_unsafe(curve->n, s, num_n_words) != 1) { return 0; } /* Calculate u1 and u2. */ - uECC_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ + mg_uecc_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ u1[num_n_words - 1] = 0; bits2int(u1, message_hash, hash_size, curve); - uECC_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ - uECC_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ + mg_uecc_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ + mg_uecc_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ /* Calculate sum = G + Q. */ - uECC_vli_set(sum, _public, num_words); - uECC_vli_set(sum + num_words, _public + num_words, num_words); - uECC_vli_set(tx, curve->G, num_words); - uECC_vli_set(ty, curve->G + num_words, num_words); - uECC_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ + mg_uecc_vli_set(sum, _public, num_words); + mg_uecc_vli_set(sum + num_words, _public + num_words, num_words); + mg_uecc_vli_set(tx, curve->G, num_words); + mg_uecc_vli_set(ty, curve->G + num_words, num_words); + mg_uecc_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ XYcZ_add(tx, ty, sum, sum + num_words, curve); - uECC_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ apply_z(sum, sum + num_words, z, curve); /* Use Shamir's trick to calculate u1*G + u2*Q */ @@ -13623,115 +14331,366 @@ int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, points[1] = curve->G; points[2] = _public; points[3] = sum; - num_bits = smax(uECC_vli_numBits(u1, num_n_words), - uECC_vli_numBits(u2, num_n_words)); - point = points[(!!uECC_vli_testBit(u1, (bitcount_t) (num_bits - 1))) | - ((!!uECC_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)]; - uECC_vli_set(rx, point, num_words); - uECC_vli_set(ry, point + num_words, num_words); - uECC_vli_clear(z, num_words); + num_bits = smax(mg_uecc_vli_numBits(u1, num_n_words), + mg_uecc_vli_numBits(u2, num_n_words)); + point = + points[(!!mg_uecc_vli_testBit(u1, (bitcount_t) (num_bits - 1))) | + ((!!mg_uecc_vli_testBit(u2, (bitcount_t) (num_bits - 1))) << 1)]; + mg_uecc_vli_set(rx, point, num_words); + mg_uecc_vli_set(ry, point + num_words, num_words); + mg_uecc_vli_clear(z, num_words); z[0] = 1; for (i = num_bits - 2; i >= 0; --i) { - uECC_word_t index; + mg_uecc_word_t index; curve->double_jacobian(rx, ry, z, curve); - index = (!!uECC_vli_testBit(u1, i)) | - (uECC_word_t) ((!!uECC_vli_testBit(u2, i)) << 1); + index = (!!mg_uecc_vli_testBit(u1, i)) | + (mg_uecc_word_t) ((!!mg_uecc_vli_testBit(u2, i)) << 1); point = points[index]; if (point) { - uECC_vli_set(tx, point, num_words); - uECC_vli_set(ty, point + num_words, num_words); + mg_uecc_vli_set(tx, point, num_words); + mg_uecc_vli_set(ty, point + num_words, num_words); apply_z(tx, ty, z, curve); - uECC_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ + mg_uecc_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ XYcZ_add(tx, ty, rx, ry, curve); - uECC_vli_modMult_fast(z, z, tz, curve); + mg_uecc_vli_modMult_fast(z, z, tz, curve); } } - uECC_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ + mg_uecc_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ apply_z(rx, ry, z, curve); /* v = x1 (mod n) */ - if (uECC_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) { - uECC_vli_sub(rx, rx, curve->n, num_n_words); + if (mg_uecc_vli_cmp_unsafe(curve->n, rx, num_n_words) != 1) { + mg_uecc_vli_sub(rx, rx, curve->n, num_n_words); } /* Accept only if v == r. */ - return (int) (uECC_vli_equal(rx, r, num_words)); + return (int) (mg_uecc_vli_equal(rx, r, num_words)); } -#if uECC_ENABLE_VLI_API +#if MG_UECC_ENABLE_VLI_API -unsigned uECC_curve_num_words(uECC_Curve curve) { +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve) { return curve->num_words; } -unsigned uECC_curve_num_bytes(uECC_Curve curve) { +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve) { return curve->num_bytes; } -unsigned uECC_curve_num_bits(uECC_Curve curve) { +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve) { return curve->num_bytes * 8; } -unsigned uECC_curve_num_n_words(uECC_Curve curve) { +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve) { return BITS_TO_WORDS(curve->num_n_bits); } -unsigned uECC_curve_num_n_bytes(uECC_Curve curve) { +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve) { return BITS_TO_BYTES(curve->num_n_bits); } -unsigned uECC_curve_num_n_bits(uECC_Curve curve) { +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve) { return curve->num_n_bits; } -const uECC_word_t *uECC_curve_p(uECC_Curve curve) { +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve) { return curve->p; } -const uECC_word_t *uECC_curve_n(uECC_Curve curve) { +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve) { return curve->n; } -const uECC_word_t *uECC_curve_G(uECC_Curve curve) { +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve) { return curve->G; } -const uECC_word_t *uECC_curve_b(uECC_Curve curve) { +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve) { return curve->b; } -#if uECC_SUPPORT_COMPRESSED_POINT -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve) { +#if MG_UECC_SUPPORT_COMPRESSED_POINT +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve) { curve->mod_sqrt(a, curve); } #endif -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, - uECC_Curve curve) { -#if (uECC_OPTIMIZATION_LEVEL > 0) +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product, + MG_UECC_Curve curve) { +#if (MG_UECC_OPTIMIZATION_LEVEL > 0) curve->mmod_fast(result, product); #else - uECC_vli_mmod(result, product, curve->p, curve->num_words); + mg_uecc_vli_mmod(result, product, curve->p, curve->num_words); #endif } -void uECC_point_mult(uECC_word_t *result, const uECC_word_t *point, - const uECC_word_t *scalar, uECC_Curve curve) { - uECC_word_t tmp1[uECC_MAX_WORDS]; - uECC_word_t tmp2[uECC_MAX_WORDS]; - uECC_word_t *p2[2] = {tmp1, tmp2}; - uECC_word_t carry = regularize_k(scalar, tmp1, tmp2, curve); +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point, + const mg_uecc_word_t *scalar, MG_UECC_Curve curve) { + mg_uecc_word_t tmp1[MG_UECC_MAX_WORDS]; + mg_uecc_word_t tmp2[MG_UECC_MAX_WORDS]; + mg_uecc_word_t *p2[2] = {tmp1, tmp2}; + mg_uecc_word_t carry = regularize_k(scalar, tmp1, tmp2, curve); EccPoint_mult(result, point, p2[!carry], 0, curve->num_n_bits + 1, curve); } -#endif /* uECC_ENABLE_VLI_API */ -#endif // MG_TLS_BUILTIN -// End of uecc BSD-2 - +#endif /* MG_UECC_ENABLE_VLI_API */ +#endif // MG_TLS_BUILTIN +// End of uecc BSD-2 + +#ifdef MG_ENABLE_LINES +#line 1 "src/tls_x25519.c" +#endif +/** + * Adapted from STROBE: https://strobe.sourceforge.io/ + * Copyright (c) 2015-2016 Cryptography Research, Inc. + * Author: Mike Hamburg + * License: MIT License + */ + + +const uint8_t X25519_BASE_POINT[X25519_BYTES] = {9}; + +#define X25519_WBITS 32 + +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; +typedef int64_t sdlimb_t; +#define LIMB(x) (uint32_t)(x##ull), (uint32_t) ((x##ull) >> 32) + +#define NLIMBS (256 / X25519_WBITS) +typedef limb_t fe[NLIMBS]; + +static limb_t umaal(limb_t *carry, limb_t acc, limb_t mand, limb_t mier) { + dlimb_t tmp = (dlimb_t) mand * mier + acc + *carry; + *carry = (limb_t) (tmp >> X25519_WBITS); + return (limb_t) tmp; +} + +// These functions are implemented in terms of umaal on ARM +static limb_t adc(limb_t *carry, limb_t acc, limb_t mand) { + dlimb_t total = (dlimb_t) *carry + acc + mand; + *carry = (limb_t) (total >> X25519_WBITS); + return (limb_t) total; +} + +static limb_t adc0(limb_t *carry, limb_t acc) { + dlimb_t total = (dlimb_t) *carry + acc; + *carry = (limb_t) (total >> X25519_WBITS); + return (limb_t) total; +} + +// - Precondition: carry is small. +// - Invariant: result of propagate is < 2^255 + 1 word +// - In particular, always less than 2p. +// - Also, output x >= min(x,19) +static void propagate(fe x, limb_t over) { + unsigned i; + limb_t carry; + over = x[NLIMBS - 1] >> (X25519_WBITS - 1) | over << 1; + x[NLIMBS - 1] &= ~((limb_t) 1 << (X25519_WBITS - 1)); + + carry = over * 19; + for (i = 0; i < NLIMBS; i++) { + x[i] = adc0(&carry, x[i]); + } +} + +static void add(fe out, const fe a, const fe b) { + unsigned i; + limb_t carry = 0; + for (i = 0; i < NLIMBS; i++) { + out[i] = adc(&carry, a[i], b[i]); + } + propagate(out, carry); +} + +static void sub(fe out, const fe a, const fe b) { + unsigned i; + sdlimb_t carry = -38; + for (i = 0; i < NLIMBS; i++) { + carry = carry + a[i] - b[i]; + out[i] = (limb_t) carry; + carry >>= X25519_WBITS; + } + propagate(out, (limb_t) (1 + carry)); +} + +// `b` can contain less than 8 limbs, thus we use `limb_t *` instead of `fe` +// to avoid build warnings +static void mul(fe out, const fe a, const limb_t *b, unsigned nb) { + limb_t accum[2 * NLIMBS] = {0}; + unsigned i, j; + + limb_t carry2; + for (i = 0; i < nb; i++) { + limb_t mand = b[i]; + carry2 = 0; + for (j = 0; j < NLIMBS; j++) { + limb_t tmp; // "a" may be misaligned + memcpy(&tmp, &a[j], sizeof(tmp)); // So make an aligned copy + accum[i + j] = umaal(&carry2, accum[i + j], mand, tmp); + } + accum[i + j] = carry2; + } + + carry2 = 0; + for (j = 0; j < NLIMBS; j++) { + out[j] = umaal(&carry2, accum[j], 38, accum[j + NLIMBS]); + } + propagate(out, carry2); +} + +static void sqr(fe out, const fe a) { + mul(out, a, a, NLIMBS); +} +static void mul1(fe out, const fe a) { + mul(out, a, out, NLIMBS); +} +static void sqr1(fe a) { + mul1(a, a); +} + +static void condswap(limb_t a[2 * NLIMBS], limb_t b[2 * NLIMBS], + limb_t doswap) { + unsigned i; + for (i = 0; i < 2 * NLIMBS; i++) { + limb_t xor_ab = (a[i] ^ b[i]) & doswap; + a[i] ^= xor_ab; + b[i] ^= xor_ab; + } +} + +// Canonicalize a field element x, reducing it to the least residue which is +// congruent to it mod 2^255-19 +// - Precondition: x < 2^255 + 1 word +static limb_t canon(fe x) { + // First, add 19. + unsigned i; + limb_t carry0 = 19; + limb_t res; + sdlimb_t carry; + for (i = 0; i < NLIMBS; i++) { + x[i] = adc0(&carry0, x[i]); + } + propagate(x, carry0); + + // Here, 19 <= x2 < 2^255 + // - This is because we added 19, so before propagate it can't be less + // than 19. After propagate, it still can't be less than 19, because if + // propagate does anything it adds 19. + // - We know that the high bit must be clear, because either the input was ~ + // 2^255 + one word + 19 (in which case it propagates to at most 2 words) or + // it was < 2^255. So now, if we subtract 19, we will get back to something in + // [0,2^255-19). + carry = -19; + res = 0; + for (i = 0; i < NLIMBS; i++) { + carry += x[i]; + res |= x[i] = (limb_t) carry; + carry >>= X25519_WBITS; + } + return (limb_t) (((dlimb_t) res - 1) >> X25519_WBITS); +} + +static const limb_t a24[1] = {121665}; + +static void ladder_part1(fe xs[5]) { + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + add(t1, x2, z2); // t1 = A + sub(z2, x2, z2); // z2 = B + add(x2, x3, z3); // x2 = C + sub(z3, x3, z3); // z3 = D + mul1(z3, t1); // z3 = DA + mul1(x2, z2); // x3 = BC + add(x3, z3, x2); // x3 = DA+CB + sub(z3, z3, x2); // z3 = DA-CB + sqr1(t1); // t1 = AA + sqr1(z2); // z2 = BB + sub(x2, t1, z2); // x2 = E = AA-BB + mul(z2, x2, a24, sizeof(a24) / sizeof(a24[0])); // z2 = E*a24 + add(z2, z2, t1); // z2 = E*a24 + AA +} + +static void ladder_part2(fe xs[5], const fe x1) { + limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + sqr1(z3); // z3 = (DA-CB)^2 + mul1(z3, x1); // z3 = x1 * (DA-CB)^2 + sqr1(x3); // x3 = (DA+CB)^2 + mul1(z2, x2); // z2 = AA*(E*a24+AA) + sub(x2, t1, x2); // x2 = BB again + mul1(x2, t1); // x2 = AA*BB +} + +static void x25519_core(fe xs[5], const uint8_t scalar[X25519_BYTES], + const uint8_t *x1, int clamp) { + int i; + limb_t swap = 0; + limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3]; + memset(xs, 0, 4 * sizeof(fe)); + x2[0] = z3[0] = 1; + memcpy(x3, x1, sizeof(fe)); + + for (i = 255; i >= 0; i--) { + uint8_t bytei = scalar[i / 8]; + limb_t doswap; + if (clamp) { + if (i / 8 == 0) { + bytei &= (uint8_t) ~7U; + } else if (i / 8 == X25519_BYTES - 1) { + bytei &= 0x7F; + bytei |= 0x40; + } + } + doswap = 0 - (limb_t) ((bytei >> (i % 8)) & 1); + condswap(x2, x3, swap ^ doswap); + swap = doswap; + + ladder_part1(xs); + ladder_part2(xs, (const limb_t *) x1); + } + condswap(x2, x3, swap); +} + +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES], + const uint8_t x1[X25519_BYTES], int clamp) { + int i, ret; + fe xs[5]; + limb_t *x2, *z2, *z3, *prev; + static const struct { + uint8_t a, c, n; + } steps[13] = {{2, 1, 1}, {2, 1, 1}, {4, 2, 3}, {2, 4, 6}, {3, 1, 1}, + {3, 2, 12}, {4, 3, 25}, {2, 3, 25}, {2, 4, 50}, {3, 2, 125}, + {3, 1, 2}, {3, 1, 2}, {3, 1, 1}}; + x25519_core(xs, scalar, x1, clamp); + + // Precomputed inversion chain + x2 = xs[0]; + z2 = xs[1]; + z3 = xs[3]; + + prev = z2; + for (i = 0; i < 13; i++) { + int j; + limb_t *a = xs[steps[i].a]; + for (j = steps[i].n; j > 0; j--) { + sqr(a, prev); + prev = a; + } + mul1(a, xs[steps[i].c]); + } + + // Here prev = z3 + // x2 /= z2 + mul((limb_t *) out, x2, z3, NLIMBS); + ret = (int) canon((limb_t *) out); + if (!clamp) ret = 0; + return ret; +} + #ifdef MG_ENABLE_LINES #line 1 "src/url.c" #endif @@ -13924,9 +14883,9 @@ int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) { memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4)); while (mg_span(acl, &entry, &acl, ',')) { uint32_t net, mask; - if (entry.ptr[0] != '+' && entry.ptr[0] != '-') return -1; - if (parse_net(&entry.ptr[1], &net, &mask) == 0) return -2; - if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.ptr[0]; + if (entry.buf[0] != '+' && entry.buf[0] != '-') return -1; + if (parse_net(&entry.buf[1], &net, &mask) == 0) return -2; + if ((mg_ntohl(remote_ip4) & mask) == net) allowed = entry.buf[0]; } } return allowed == '+'; @@ -14026,7 +14985,7 @@ static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey, mg_sha1_ctx sha_ctx; mg_sha1_init(&sha_ctx); - mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len); + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->buf, wskey->len); mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36); mg_sha1_final(sha, &sha_ctx); mg_base64_encode(sha, sizeof(sha), (char *) b64_sha, sizeof(b64_sha)); @@ -14039,7 +14998,7 @@ static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey, if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); if (wsproto != NULL) { mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len, - wsproto->ptr); + wsproto->buf); } mg_send(c, "\r\n", 2); } @@ -14163,7 +15122,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) { size_t len = msg.header_len + msg.data_len; uint8_t final = msg.flags & 128, op = msg.flags & 15; // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op, - // (int) m.data.len, (int) m.data.len, m.data.ptr)); + // (int) m.data.len, (int) m.data.len, m.data.buf)); switch (op) { case WEBSOCKET_OP_CONTINUE: mg_call(c, MG_EV_WS_CTL, &m); @@ -14184,7 +15143,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) { MG_DEBUG(("%lu WS CLOSE", c->id)); mg_call(c, MG_EV_WS_CTL, &m); // Echo the payload of the received CLOSE message back to the sender - mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE); + mg_ws_send(c, m.data.buf, m.data.len, WEBSOCKET_OP_CLOSE); c->is_draining = 1; break; default: @@ -14234,7 +15193,7 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url, "Connection: Upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" "Sec-WebSocket-Key: %s\r\n", - mg_url_uri(url), (int) host.len, host.ptr, key); + mg_url_uri(url), (int) host.len, host.buf, key); if (fmt != NULL) { va_list ap; va_start(ap, fmt); @@ -14431,10 +15390,15 @@ struct imxrt_enet { }; #undef ENET +#if defined(MG_DRIVER_IMXRT_RT11) && MG_DRIVER_IMXRT_RT11 +#define ENET ((struct imxrt_enet *) (uintptr_t) 0x40424000U) +#define ETH_DESC_CNT 5 // Descriptors count +#else #define ENET ((struct imxrt_enet *) (uintptr_t) 0x402D8000U) +#define ETH_DESC_CNT 4 // Descriptors count +#endif #define ETH_PKT_SIZE 1536 // Max frame size, 64-bit aligned -#define ETH_DESC_CNT 4 // Descriptors count struct enet_desc { uint16_t length; // Data length @@ -14453,33 +15417,20 @@ static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED; static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_64BYTE_ALIGNED; static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_ID1 = 2, - MG_PHYREG_ID2 = 3 -}; - -static uint16_t enet_phy_read(uint8_t addr, uint8_t reg) { +static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) { ENET->EIR |= MG_BIT(23); // MII interrupt clear ENET->MMFR = (1 << 30) | (2 << 28) | (addr << 23) | (reg << 18) | (2 << 16); while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0; return ENET->MMFR & 0xffff; } -static void enet_phy_write(uint8_t addr, uint8_t reg, uint16_t val) { +static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { ENET->EIR |= MG_BIT(23); // MII interrupt clear ENET->MMFR = (1 << 30) | (1 << 28) | (addr << 23) | (reg << 18) | (2 << 16) | val; while ((ENET->EIR & MG_BIT(23)) == 0) (void) 0; } -static uint32_t enet_phy_id(uint8_t addr) { - uint16_t phy_id1 = enet_phy_read(addr, MG_PHYREG_ID1); - uint16_t phy_id2 = enet_phy_read(addr, MG_PHYREG_ID2); - return (uint32_t) phy_id1 << 16 | phy_id2; -} - // MDC clock is generated from IPS Bus clock (ipg_clk); as per 802.3, // it must not exceed 2.5MHz // The PHY receives the PLL6-generated 50MHz clock @@ -14509,28 +15460,8 @@ static bool mg_tcpip_driver_imxrt_init(struct mg_tcpip_if *ifp) { // TODO(): Otherwise, guess (currently assuming max freq) int cr = (d == NULL || d->mdc_cr < 0) ? 24 : d->mdc_cr; ENET->MSCR = (1 << 8) | ((cr & 0x3f) << 1); // HOLDTIME 2 clks - - enet_phy_write(d->phy_addr, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - enet_phy_write(d->phy_addr, MG_PHYREG_BCR, - MG_BIT(12)); // Set autonegotiation - - // PHY: Enable 50 MHz external ref clock at XI (preserve defaults) - uint32_t id = enet_phy_id(d->phy_addr); - MG_INFO(("PHY ID: %#04x %#04x", (uint16_t) (id >> 16), (uint16_t) id)); - // 2000 a140 - TI DP83825I - // 0007 c0fx - LAN8720 - // 0022 1561 - KSZ8081RNB - - if ((id & 0xffff0000) == 0x220000) { // KSZ8081RNB, like EVK-RTxxxx boards - enet_phy_write(d->phy_addr, 31, - MG_BIT(15) | MG_BIT(8) | MG_BIT(7)); // PC2R - } else if ((id & 0xffff0000) == 0x20000000) { // DP83825I, like Teensy4.1 - enet_phy_write(d->phy_addr, 23, 0x81); // 50MHz clock input - enet_phy_write(d->phy_addr, 24, 0x280); // LED status, active high - } else { // Default to LAN8720 - MG_INFO(("Defaulting to LAN8720 PHY...")); // TODO() - } - + struct mg_phy phy = {enet_read_phy, enet_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_LEDS_ACTIVE_HIGH); // MAC clocks PHY // Select RMII mode, 100M, keep CRC, set max rx length, disable loop ENET->RCR = (1518 << 16) | MG_BIT(8) | MG_BIT(2); // ENET->RCR |= MG_BIT(3); // Receive all @@ -14556,10 +15487,10 @@ static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { static int s_txno; // Current descriptor index if (len > sizeof(s_txbuf[ETH_DESC_CNT])) { - ifp->nerr++; MG_ERROR(("Frame too big, %ld", (long) len)); len = (size_t) -1; // fail } else if ((s_txdesc[s_txno].control & MG_BIT(15))) { + ifp->nerr++; MG_ERROR(("No descriptors available")); len = 0; // retry later } else { @@ -14578,28 +15509,18 @@ static size_t mg_tcpip_driver_imxrt_tx(const void *buf, size_t len, static bool mg_tcpip_driver_imxrt_up(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_imxrt_data *d = (struct mg_tcpip_driver_imxrt_data *) ifp->driver_data; - uint32_t bsr = enet_phy_read(d->phy_addr, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {enet_read_phy, enet_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up // tmp = reg with flags set to the most likely situation: 100M full-duplex // if(link is slow or half) set flags otherwise // reg = tmp - uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex - uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M - uint32_t phy_id = enet_phy_id(d->phy_addr); - if ((phy_id & 0xffff0000) == 0x220000) { // KSZ8081RNB - uint16_t pc1r = enet_phy_read(d->phy_addr, 30); // Read PC1R - if ((pc1r & 3) == 1) rcr |= MG_BIT(9); // 10M - if ((pc1r & MG_BIT(2)) == 0) tcr &= ~MG_BIT(2); // Half-duplex - } else if ((phy_id & 0xffff0000) == 0x20000000) { // DP83825I - uint16_t physts = enet_phy_read(d->phy_addr, 16); // Read PHYSTS - if (physts & MG_BIT(1)) rcr |= MG_BIT(9); // 10M - if ((physts & MG_BIT(2)) == 0) tcr &= ~MG_BIT(2); // Half-duplex - } else { // Default to LAN8720 - uint16_t scsr = enet_phy_read(d->phy_addr, 31); // Read CSCR - if ((scsr & MG_BIT(3)) == 0) rcr |= MG_BIT(9); // 10M - if ((scsr & MG_BIT(4)) == 0) tcr &= ~MG_BIT(2); // Half-duplex - } + uint32_t tcr = ENET->TCR | MG_BIT(2); // Full-duplex + uint32_t rcr = ENET->RCR & ~MG_BIT(9); // 100M + if (speed == MG_PHY_SPEED_10M) rcr |= MG_BIT(9); // 10M + if (full_duplex == false) tcr &= ~MG_BIT(2); // Half-duplex ENET->TCR = tcr; // IRQ handler does not fiddle with these registers ENET->RCR = rcr; MG_DEBUG(("Link is %uM %s-duplex", rcr & MG_BIT(9) ? 10 : 100, @@ -14635,6 +15556,151 @@ struct mg_tcpip_driver mg_tcpip_driver_imxrt = {mg_tcpip_driver_imxrt_init, #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/phy.c" +#endif + + +enum { // ID1 ID2 + MG_PHY_KSZ8x = 0x22, // 0022 1561 - KSZ8081RNB + MG_PHY_DP83x = 0x2000, // 2000 a140 - TI DP83825I + MG_PHY_DP83867 = 0xa231, // 2000 a231 - TI DP83867I + MG_PHY_LAN87x = 0x7, // 0007 c0fx - LAN8720 + MG_PHY_RTL8201 = 0x1C // 001c c816 - RTL8201 +}; + +enum { + MG_PHY_REG_BCR = 0, + MG_PHY_REG_BSR = 1, + MG_PHY_REG_ID1 = 2, + MG_PHY_REG_ID2 = 3, + MG_PHY_DP83x_REG_PHYSTS = 16, + MG_PHY_DP83867_REG_PHYSTS = 17, + MG_PHY_DP83x_REG_RCSR = 23, + MG_PHY_DP83x_REG_LEDCR = 24, + MG_PHY_KSZ8x_REG_PC1R = 30, + MG_PHY_KSZ8x_REG_PC2R = 31, + MG_PHY_LAN87x_REG_SCSR = 31, + MG_PHY_RTL8201_REG_RMSR = 16, // in page 7 + MG_PHY_RTL8201_REG_PAGESEL = 31 +}; + +static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) { + switch (id1) { + case MG_PHY_DP83x: + switch (id2) { + case MG_PHY_DP83867: + return "DP83867"; + default: + return "DP83x"; + } + case MG_PHY_KSZ8x: + return "KSZ8x"; + case MG_PHY_LAN87x: + return "LAN87x"; + case MG_PHY_RTL8201: + return "RTL8201"; + default: + return "unknown"; + } + (void) id2; +} + +static void mg_phy_set_clk_out(struct mg_phy *phy, uint8_t phy_addr) { + uint16_t id1, id2; + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1); + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2); + + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) { + // write 0x10d to IO_MUX_CFG (0x0170) + phy->write_reg(phy_addr, 0x0d, 0x1f); + phy->write_reg(phy_addr, 0x0e, 0x170); + phy->write_reg(phy_addr, 0x0d, 0x401f); + phy->write_reg(phy_addr, 0x0e, 0x10d); + } +} + +void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) { + uint16_t id1, id2; + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(15)); // Reset PHY + phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(12)); // Autonegotiation + + id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1); + id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2); + MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2))); + + if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) { + mg_phy_set_clk_out(phy, phy_addr); + } + + if (config & MG_PHY_CLOCKS_MAC) { + // Use PHY crystal oscillator (preserve defaults) + // nothing to do + } else { // MAC clocks PHY, PHY has no xtal + // Enable 50 MHz external ref clock at XI (preserve defaults) + if (id1 == MG_PHY_DP83x && id2 != MG_PHY_DP83867) { + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_RCSR, MG_BIT(7) | MG_BIT(0)); + } else if (id1 == MG_PHY_KSZ8x) { + phy->write_reg(phy_addr, MG_PHY_KSZ8x_REG_PC2R, + MG_BIT(15) | MG_BIT(8) | MG_BIT(7)); + } else if (id1 == MG_PHY_LAN87x) { + // nothing to do + } else if (id1 == MG_PHY_RTL8201) { + // assume PHY has been hardware strapped properly +#if 0 + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 7); // Select page 7 + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_RMSR, 0x1ffa); + phy->write_reg(phy_addr, MG_PHY_RTL8201_REG_PAGESEL, 0); // Select page 0 +#endif + } + } + + if (config & MG_PHY_LEDS_ACTIVE_HIGH && id1 == MG_PHY_DP83x) { + phy->write_reg(phy_addr, MG_PHY_DP83x_REG_LEDCR, + MG_BIT(9) | MG_BIT(7)); // LED status, active high + } // Other PHYs do not support this feature +} + +bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex, + uint8_t *speed) { + bool up = false; + uint16_t bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); + if ((bsr & MG_BIT(5)) && !(bsr & MG_BIT(2))) // some PHYs latch down events + bsr = phy->read_reg(phy_addr, MG_PHY_REG_BSR); // read again + up = bsr & MG_BIT(2); + if (up && full_duplex != NULL && speed != NULL) { + uint16_t id1 = phy->read_reg(phy_addr, MG_PHY_REG_ID1); + if (id1 == MG_PHY_DP83x) { + uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2); + if (id2 == MG_PHY_DP83867) { + uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83867_REG_PHYSTS); + *full_duplex = physts & MG_BIT(13); + *speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M + : (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M + : MG_PHY_SPEED_10M; + } else { + uint16_t physts = phy->read_reg(phy_addr, MG_PHY_DP83x_REG_PHYSTS); + *full_duplex = physts & MG_BIT(2); + *speed = (physts & MG_BIT(1)) ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M; + } + } else if (id1 == MG_PHY_KSZ8x) { + uint16_t pc1r = phy->read_reg(phy_addr, MG_PHY_KSZ8x_REG_PC1R); + *full_duplex = pc1r & MG_BIT(2); + *speed = (pc1r & 3) == 1 ? MG_PHY_SPEED_10M : MG_PHY_SPEED_100M; + } else if (id1 == MG_PHY_LAN87x) { + uint16_t scsr = phy->read_reg(phy_addr, MG_PHY_LAN87x_REG_SCSR); + *full_duplex = scsr & MG_BIT(4); + *speed = (scsr & MG_BIT(3)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M; + } else if (id1 == MG_PHY_RTL8201) { + uint16_t bcr = phy->read_reg(phy_addr, MG_PHY_REG_BCR); + if (bcr & MG_BIT(15)) return 0; // still resetting + *full_duplex = bcr & MG_BIT(8); + *speed = (bcr & MG_BIT(13)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M; + } + } + return up; +} + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/ra.c" #endif @@ -14681,13 +15747,6 @@ static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED; static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_32BYTE_ALIGNED; static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_ID1 = 2, - MG_PHYREG_ID2 = 3 -}; - // fastest is 3 cycles (SUB + BNE) on a 3-stage pipeline or equivalent static inline void raspin(volatile uint32_t count) { while (count--) (void) 0; @@ -14748,7 +15807,7 @@ static uint16_t smi_rd(uint16_t header) { pir = 0; // read, mdc = 0 ETHERC->PIR = pir; raspin(s_smispin / 2); // 1/4 clock period, 300ns max access time - data |= ETHERC->PIR & MG_BIT(3) ? 1 : 0; // read mdio + data |= (uint16_t)(ETHERC->PIR & MG_BIT(3) ? 1 : 0); // read mdio raspin(s_smispin / 2); // 1/4 clock period pir |= MG_BIT(0); // mdc = 1 ETHERC->PIR = pir; @@ -14757,18 +15816,12 @@ static uint16_t smi_rd(uint16_t header) { return data; } -static uint16_t raeth_phy_read(uint8_t addr, uint8_t reg) { - return smi_rd((1 << 14) | (2 << 12) | (addr << 7) | (reg << 2) | (2 << 0)); +static uint16_t raeth_read_phy(uint8_t addr, uint8_t reg) { + return smi_rd((uint16_t)((1 << 14) | (2 << 12) | (addr << 7) | (reg << 2) | (2 << 0))); } -static void raeth_phy_write(uint8_t addr, uint8_t reg, uint16_t val) { - smi_wr((1 << 14) | (1 << 12) | (addr << 7) | (reg << 2) | (2 << 0), val); -} - -static uint32_t raeth_phy_id(uint8_t addr) { - uint16_t phy_id1 = raeth_phy_read(addr, MG_PHYREG_ID1); - uint16_t phy_id2 = raeth_phy_read(addr, MG_PHYREG_ID2); - return (uint32_t) phy_id1 << 16 | phy_id2; +static void raeth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + smi_wr((uint16_t)((1 << 14) | (1 << 12) | (addr << 7) | (reg << 2) | (2 << 0)), val); } // MDC clock is generated manually; as per 802.3, it must not exceed 2.5MHz @@ -14804,27 +15857,8 @@ static bool mg_tcpip_driver_ra_init(struct mg_tcpip_if *ifp) { EDMAC->EDMR = MG_BIT(6); // Initialize, little-endian (27.2.1) MG_DEBUG(("PHY addr: %d, smispin: %d", d->phy_addr, s_smispin)); - raeth_phy_write(d->phy_addr, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - raeth_phy_write(d->phy_addr, MG_PHYREG_BCR, - MG_BIT(12)); // Set autonegotiation - - // PHY: Enable ref clock (preserve defaults) - uint32_t id = raeth_phy_id(d->phy_addr); - MG_INFO(("PHY ID: %#04x %#04x", (uint16_t) (id >> 16), (uint16_t) id)); - // 2000 a140 - TI DP83825I - // 0007 c0fx - LAN8720 - // 0022 156x - KSZ8081RNB/KSZ8091RNB - - if ((id & 0xffff0000) == 0x220000) { // KSZ8091RNB, like EK-RA6Mx boards - // 25 MHz xtal at XI/XO (default) - raeth_phy_write(d->phy_addr, 31, MG_BIT(15) | MG_BIT(8)); // PC2R - } else if ((id & 0xffff0000) == 0x20000000) { // DP83825I, like ??? - // 50 MHz external at XI ??? - raeth_phy_write(d->phy_addr, 23, 0x81); // 50MHz clock input - raeth_phy_write(d->phy_addr, 24, 0x280); // LED status, active high - } else { // Default to LAN8720 - MG_INFO(("Defaulting to LAN8720 PHY...")); // TODO() - } + struct mg_phy phy = {raeth_read_phy, raeth_write_phy}; + mg_phy_init(&phy, d->phy_addr, 0); // MAC clocks PHY // Select RMII mode, ETHERC->ECMR = MG_BIT(2) | MG_BIT(1); // 100M, Full-duplex, CRC @@ -14854,10 +15888,10 @@ static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { static int s_txno; // Current descriptor index if (len > sizeof(s_txbuf[ETH_DESC_CNT])) { - ifp->nerr++; MG_ERROR(("Frame too big, %ld", (long) len)); len = (size_t) -1; // fail } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { + ifp->nerr++; MG_ERROR(("No descriptors available")); len = 0; // retry later } else { @@ -14873,27 +15907,17 @@ static size_t mg_tcpip_driver_ra_tx(const void *buf, size_t len, static bool mg_tcpip_driver_ra_up(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_ra_data *d = (struct mg_tcpip_driver_ra_data *) ifp->driver_data; - uint32_t bsr = raeth_phy_read(d->phy_addr, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {raeth_read_phy, raeth_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up // tmp = reg with flags set to the most likely situation: 100M full-duplex // if(link is slow or half) set flags otherwise // reg = tmp uint32_t ecmr = ETHERC->ECMR | MG_BIT(2) | MG_BIT(1); // 100M Full-duplex - uint32_t phy_id = raeth_phy_id(d->phy_addr); - if ((phy_id & 0xffff0000) == 0x220000) { // KSZ8091RNB - uint16_t pc1r = raeth_phy_read(d->phy_addr, 30); // Read PC1R - if ((pc1r & 3) == 1) ecmr &= ~MG_BIT(2); // 10M - if ((pc1r & MG_BIT(2)) == 0) ecmr &= ~MG_BIT(1); // Half-duplex - } else if ((phy_id & 0xffff0000) == 0x20000000) { // DP83825I - uint16_t physts = raeth_phy_read(d->phy_addr, 16); // Read PHYSTS - if (physts & MG_BIT(1)) ecmr &= ~MG_BIT(2); // 10M - if ((physts & MG_BIT(2)) == 0) ecmr &= ~MG_BIT(1); // Half-duplex - } else { // Default to LAN8720 - uint16_t scsr = raeth_phy_read(d->phy_addr, 31); // Read CSCR - if ((scsr & MG_BIT(3)) == 0) ecmr &= ~MG_BIT(2); // 10M - if ((scsr & MG_BIT(4)) == 0) ecmr &= ~MG_BIT(1); // Half-duplex - } + if (speed == MG_PHY_SPEED_10M) ecmr &= ~MG_BIT(2); // 10M + if (full_duplex == false) ecmr &= ~MG_BIT(1); // Half-duplex ETHERC->ECMR = ecmr; // IRQ handler does not fiddle with these registers MG_DEBUG(("Link is %uM %s-duplex", ecmr & MG_BIT(2) ? 100 : 10, ecmr & MG_BIT(1) ? "full" : "half")); @@ -14936,7 +15960,8 @@ struct mg_tcpip_driver mg_tcpip_driver_ra = {mg_tcpip_driver_ra_init, #endif -#if defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54 +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54 + #include #define ETH_PKT_SIZE 1536 // Max frame size @@ -15184,23 +16209,16 @@ static uint8_t s_txno; // Current TX descriptor static uint8_t s_rxno; // Current RX descriptor static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_ID1 = 2, - MG_PHYREG_ID2 = 3, - MG_PHYREG_CSCR = 31 -}; -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { ETH->MACMIIAR &= (7 << 2); ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); ETH->MACMIIAR |= MG_BIT(0); while (ETH->MACMIIAR & MG_BIT(0)) (void) 0; - return ETH->MACMIIDR; + return ETH->MACMIIDR & 0xffff; } -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { ETH->MACMIIDR = val; ETH->MACMIIAR &= (7 << 2); ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); @@ -15298,19 +16316,16 @@ static bool mg_tcpip_driver_stm32f_init(struct mg_tcpip_if *ifp) { ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause // ETH->MACFFR = MG_BIT(31); // Receive all - eth_write_phy(phy_addr, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - eth_write_phy(phy_addr, MG_PHYREG_BCR, MG_BIT(12)); // Set autonegotiation - ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors - ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors - ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC); + ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors + ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc; // RX descriptors + ETH->DMAIER = MG_BIT(6) | MG_BIT(16); // RIE, NISE ETH->MACCR = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast ETH->DMAOMR = MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF - MG_DEBUG(("PHY ID: %#04hx %#04hx", eth_read_phy(phy_addr, MG_PHYREG_ID1), - eth_read_phy(phy_addr, MG_PHYREG_ID2))); - // MAC address filtering ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) | @@ -15346,16 +16361,17 @@ static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_stm32f_data *d = (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; - uint32_t bsr = eth_read_phy(phy_addr, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed); if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up - uint32_t scsr = eth_read_phy(phy_addr, MG_PHYREG_CSCR); // tmp = reg with flags set to the most likely situation: 100M full-duplex // if(link is slow or half) set flags otherwise // reg = tmp uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex - if ((scsr & MG_BIT(3)) == 0) maccr &= ~MG_BIT(14); // 10M - if ((scsr & MG_BIT(4)) == 0) maccr &= ~MG_BIT(11); // Half-duplex + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M + if (full_duplex == false) maccr &= ~MG_BIT(11); // Half-duplex ETH->MACCR = maccr; // IRQ handler does not fiddle with this register MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, maccr & MG_BIT(11) ? "full" : "half")); @@ -15445,22 +16461,16 @@ static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers static struct mg_tcpip_if *s_ifp; // MIP interface -enum { - MG_PHY_ADDR = 0, - MG_PHYREG_BCR = 0, - MG_PHYREG_BSR = 1, - MG_PHYREG_CSCR = 31 -}; // PHY constants -static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { ETH->MACMDIOAR &= (0xF << 8); ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2; ETH->MACMDIOAR |= MG_BIT(0); while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; - return ETH->MACMDIODR; + return (uint16_t) ETH->MACMDIODR; } -static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { ETH->MACMDIODR = val; ETH->MACMDIOAR &= (0xF << 8); ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2; @@ -15548,6 +16558,8 @@ static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_stm32h_data *d = (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; s_ifp = ifp; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf; // Init RX descriptors for (int i = 0; i < ETH_DESC_CNT; i++) { @@ -15574,9 +16586,8 @@ static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) { ETH->MACIER = 0; // Do not enable additional irq sources (reset value) ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause // ETH->MACPFR = MG_BIT(31); // Receive all - eth_write_phy(MG_PHY_ADDR, MG_PHYREG_BCR, MG_BIT(15)); // Reset PHY - eth_write_phy(MG_PHY_ADDR, MG_PHYREG_BCR, - MG_BIT(12)); // Set autonegotiation + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, phy_addr, phy_conf); ETH->DMACRDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors start address ETH->DMACRDRLR = ETH_DESC_CNT - 1; // ring length @@ -15631,16 +16642,20 @@ static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len, } static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) { - uint32_t bsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_BSR); - bool up = bsr & MG_BIT(2) ? 1 : 0; + struct mg_tcpip_driver_stm32h_data *d = + (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; + uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, phy_addr, &full_duplex, &speed); if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up - uint32_t scsr = eth_read_phy(MG_PHY_ADDR, MG_PHYREG_CSCR); // tmp = reg with flags set to the most likely situation: 100M full-duplex // if(link is slow or half) set flags otherwise // reg = tmp uint32_t maccr = ETH->MACCR | MG_BIT(14) | MG_BIT(13); // 100M, Full-duplex - if ((scsr & MG_BIT(3)) == 0) maccr &= ~MG_BIT(14); // 10M - if ((scsr & MG_BIT(4)) == 0) maccr &= ~MG_BIT(13); // Half-duplex + if (speed == MG_PHY_SPEED_10M) maccr &= ~MG_BIT(14); // 10M + if (full_duplex == false) maccr &= ~MG_BIT(13); // Half-duplex ETH->MACCR = maccr; // IRQ handler does not fiddle with this register MG_DEBUG(("Link is %uM %s-duplex", maccr & MG_BIT(14) ? 100 : 10, maccr & MG_BIT(13) ? "full" : "half")); @@ -15939,7 +16954,7 @@ struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init, #endif -#if MG_ENABLE_TCPIP +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500 enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 }; @@ -16029,3 +17044,453 @@ static bool w5500_up(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, w5500_up}; #endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/xmc.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC + +struct ETH_GLOBAL_TypeDef { + volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH, + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION, + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2], + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW, + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW, + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL, + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK, + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2, + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK, + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30], + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT, + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS, + SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE, + TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS, + SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS, + PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND, + RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS, + TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE, + INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER, + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS, + RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR, + CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS, + CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE; +}; + +#undef ETH0 +#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL) + +#define ETH_PKT_SIZE 1536 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) + +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | + ((uint32_t)addr << 11) | + ((uint32_t)reg << 6) | 1; + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; + return (uint16_t)(ETH0->GMII_DATA & 0xffff); +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH0->GMII_DATA = val; + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | + ((uint32_t)addr << 11) | + ((uint32_t)reg << 6) | 3; + while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; +} + +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc_data *d) { + if (d->mdc_cr == -1) { + // assume ETH clock is 60MHz by default + // then according to 13.2.8.1, we need to set value 3 + return 3; + } + + return d->mdc_cr; +} + +static bool mg_tcpip_driver_xmc_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc_data *d = + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data; + s_ifp = ifp; + + // reset MAC + ETH0->BUS_MODE |= 1; + while (ETH0->BUS_MODE & 1) (void) 0; + + // set clock rate + ETH0->GMII_ADDRESS = get_clock_rate(d) << 2; + + // init phy + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + + // configure MAC: DO, DM, FES, TC + ETH0->MAC_CONFIGURATION = MG_BIT(13) | MG_BIT(11) | MG_BIT(14) | MG_BIT(24); + + // set the MAC address + ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]); + ETH0->MAC_ADDRESS0_LOW = + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]); + + // Configure the receive filter + ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC + // Disable flow control + ETH0->FLOW_CONTROL = 0; + // Enable store and forward mode + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF + + // Configure DMA bus mode (AAL, USP, RPBL, PBL) + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8); + + // init RX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor + s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE; + s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[0][0]; + } else { + s_rxdesc[i][3] = (uint32_t) &s_rxdesc[i + 1][0]; + } + } + ETH0->RECEIVE_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_rxdesc[0][0]; + + // init TX descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_txdesc[i][0] = MG_BIT(30) | MG_BIT(20); + s_txdesc[i][2] = (uint32_t) s_txbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_txdesc[i][3] = (uint32_t) &s_txdesc[0][0]; + } else { + s_txdesc[i][3] = (uint32_t) &s_txdesc[i + 1][0]; + } + } + ETH0->TRANSMIT_DESCRIPTOR_LIST_ADDRESS = (uint32_t) &s_txdesc[0][0]; + + // Clear interrupts + ETH0->STATUS = 0xFFFFFFFF; + + // Disable MAC interrupts + ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM + + //Enable interrupts (NIE, RIE, TIE) + ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0); + + // Enable MAC transmission and reception (TE, RE) + ETH0->MAC_CONFIGURATION |= MG_BIT(3) | MG_BIT(2); + // Enable DMA transmission and reception (ST, SR) + ETH0->OPERATION_MODE |= MG_BIT(13) | MG_BIT(1); + return true; +} + +static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if ((s_txdesc[s_txno][0] & MG_BIT(31))) { + ifp->nerr++; + MG_ERROR(("No free descriptors")); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); + s_txdesc[s_txno][1] = len; + // Table 13-19 Transmit Descriptor Word 0 (IC, LS, FS, TCH) + s_txdesc[s_txno][0] = MG_BIT(30) | MG_BIT(29) | MG_BIT(28) | MG_BIT(20); + s_txdesc[s_txno][0] |= MG_BIT(31); // OWN bit: handle control to DMA + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + + // Resume processing + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable + ETH0->TRANSMIT_POLL_DEMAND = 0; + return len; +} + +static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc_data *d = + (struct mg_tcpip_driver_xmc_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 100, + full_duplex ? "full" : "half")); + } + return up; +} + +void ETH0_IRQHandler(void); +void ETH0_IRQHandler(void) { + uint32_t irq_status = ETH0->STATUS; + + // check if a frame was received + if (irq_status & MG_BIT(6)) { + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { + if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) { + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16; + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to DMA + // Resume processing + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI + ETH0->RECEIVE_POLL_DEMAND = 0; + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + ETH0->STATUS = MG_BIT(6); + } + + // clear Successful transmission interrupt + if (irq_status & 1) { + ETH0->STATUS = 1; + } + + // clear normal interrupt + if (irq_status & MG_BIT(16)) { + ETH0->STATUS = MG_BIT(16); + } +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc = { + mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL, + mg_tcpip_driver_xmc_up}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/xmc7.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 + +struct ETH_Type { + volatile uint32_t CTL, STATUS, RESERVED[1022], NETWORK_CONTROL, + NETWORK_CONFIG, NETWORK_STATUS, USER_IO_REGISTER, DMA_CONFIG, + TRANSMIT_STATUS, RECEIVE_Q_PTR, TRANSMIT_Q_PTR, RECEIVE_STATUS, + INT_STATUS, INT_ENABLE, INT_DISABLE, INT_MASK, PHY_MANAGEMENT, PAUSE_TIME, + TX_PAUSE_QUANTUM, PBUF_TXCUTTHRU, PBUF_RXCUTTHRU, JUMBO_MAX_LENGTH, + EXTERNAL_FIFO_INTERFACE, RESERVED1, AXI_MAX_PIPELINE, RSC_CONTROL, + INT_MODERATION, SYS_WAKE_TIME, RESERVED2[7], HASH_BOTTOM, HASH_TOP, + SPEC_ADD1_BOTTOM, SPEC_ADD1_TOP, SPEC_ADD2_BOTTOM, SPEC_ADD2_TOP, + SPEC_ADD3_BOTTOM, SPEC_ADD3_TOP, SPEC_ADD4_BOTTOM, SPEC_ADD4_TOP, + SPEC_TYPE1, SPEC_TYPE2, SPEC_TYPE3, SPEC_TYPE4, WOL_REGISTER, + STRETCH_RATIO, STACKED_VLAN, TX_PFC_PAUSE, MASK_ADD1_BOTTOM, + MASK_ADD1_TOP, DMA_ADDR_OR_MASK, RX_PTP_UNICAST, TX_PTP_UNICAST, + TSU_NSEC_CMP, TSU_SEC_CMP, TSU_MSB_SEC_CMP, TSU_PTP_TX_MSB_SEC, + TSU_PTP_RX_MSB_SEC, TSU_PEER_TX_MSB_SEC, TSU_PEER_RX_MSB_SEC, + DPRAM_FILL_DBG, REVISION_REG, OCTETS_TXED_BOTTOM, OCTETS_TXED_TOP, + FRAMES_TXED_OK, BROADCAST_TXED, MULTICAST_TXED, PAUSE_FRAMES_TXED, + FRAMES_TXED_64, FRAMES_TXED_65, FRAMES_TXED_128, FRAMES_TXED_256, + FRAMES_TXED_512, FRAMES_TXED_1024, FRAMES_TXED_1519, TX_UNDERRUNS, + SINGLE_COLLISIONS, MULTIPLE_COLLISIONS, EXCESSIVE_COLLISIONS, + LATE_COLLISIONS, DEFERRED_FRAMES, CRS_ERRORS, OCTETS_RXED_BOTTOM, + OCTETS_RXED_TOP, FRAMES_RXED_OK, BROADCAST_RXED, MULTICAST_RXED, + PAUSE_FRAMES_RXED, FRAMES_RXED_64, FRAMES_RXED_65, FRAMES_RXED_128, + FRAMES_RXED_256, FRAMES_RXED_512, FRAMES_RXED_1024, FRAMES_RXED_1519, + UNDERSIZE_FRAMES, EXCESSIVE_RX_LENGTH, RX_JABBERS, FCS_ERRORS, + RX_LENGTH_ERRORS, RX_SYMBOL_ERRORS, ALIGNMENT_ERRORS, RX_RESOURCE_ERRORS, + RX_OVERRUNS, RX_IP_CK_ERRORS, RX_TCP_CK_ERRORS, RX_UDP_CK_ERRORS, + AUTO_FLUSHED_PKTS, RESERVED3, TSU_TIMER_INCR_SUB_NSEC, TSU_TIMER_MSB_SEC, + TSU_STROBE_MSB_SEC, TSU_STROBE_SEC, TSU_STROBE_NSEC, TSU_TIMER_SEC, + TSU_TIMER_NSEC, TSU_TIMER_ADJUST, TSU_TIMER_INCR, TSU_PTP_TX_SEC, + TSU_PTP_TX_NSEC, TSU_PTP_RX_SEC, TSU_PTP_RX_NSEC, TSU_PEER_TX_SEC, + TSU_PEER_TX_NSEC, TSU_PEER_RX_SEC, TSU_PEER_RX_NSEC, PCS_CONTROL, + PCS_STATUS, RESERVED4[2], PCS_AN_ADV, PCS_AN_LP_BASE, PCS_AN_EXP, + PCS_AN_NP_TX, PCS_AN_LP_NP, RESERVED5[6], PCS_AN_EXT_STATUS, RESERVED6[8], + TX_PAUSE_QUANTUM1, TX_PAUSE_QUANTUM2, TX_PAUSE_QUANTUM3, RESERVED7, + RX_LPI, RX_LPI_TIME, TX_LPI, TX_LPI_TIME, DESIGNCFG_DEBUG1, + DESIGNCFG_DEBUG2, DESIGNCFG_DEBUG3, DESIGNCFG_DEBUG4, DESIGNCFG_DEBUG5, + DESIGNCFG_DEBUG6, DESIGNCFG_DEBUG7, DESIGNCFG_DEBUG8, DESIGNCFG_DEBUG9, + DESIGNCFG_DEBUG10, RESERVED8[22], SPEC_ADD5_BOTTOM, SPEC_ADD5_TOP, + RESERVED9[60], SPEC_ADD36_BOTTOM, SPEC_ADD36_TOP, INT_Q1_STATUS, + INT_Q2_STATUS, INT_Q3_STATUS, RESERVED10[11], INT_Q15_STATUS, RESERVED11, + TRANSMIT_Q1_PTR, TRANSMIT_Q2_PTR, TRANSMIT_Q3_PTR, RESERVED12[11], + TRANSMIT_Q15_PTR, RESERVED13, RECEIVE_Q1_PTR, RECEIVE_Q2_PTR, + RECEIVE_Q3_PTR, RESERVED14[3], RECEIVE_Q7_PTR, RESERVED15, + DMA_RXBUF_SIZE_Q1, DMA_RXBUF_SIZE_Q2, DMA_RXBUF_SIZE_Q3, RESERVED16[3], + DMA_RXBUF_SIZE_Q7, CBS_CONTROL, CBS_IDLESLOPE_Q_A, CBS_IDLESLOPE_Q_B, + UPPER_TX_Q_BASE_ADDR, TX_BD_CONTROL, RX_BD_CONTROL, UPPER_RX_Q_BASE_ADDR, + RESERVED17[2], HIDDEN_REG0, HIDDEN_REG1, HIDDEN_REG2, HIDDEN_REG3, + RESERVED18[2], HIDDEN_REG4, HIDDEN_REG5; +}; + +#define ETH0 ((struct ETH_Type *) 0x40490000) + +#define ETH_PKT_SIZE 1536 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 2 // Descriptor size (words) + +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor + +static struct mg_tcpip_if *s_ifp; // MIP interface +enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; + +static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { + // WRITE1, READ OPERATION, PHY, REG, WRITE10 + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(29) | ((addr & 0xf) << 24) | + ((reg & 0x1f) << 18) | MG_BIT(17); + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0; + return ETH0->PHY_MANAGEMENT & 0xffff; +} + +static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + ETH0->PHY_MANAGEMENT = MG_BIT(30) | MG_BIT(28) | ((addr & 0xf) << 24) | + ((reg & 0x1f) << 18) | MG_BIT(17) | val; + while ((ETH0->NETWORK_STATUS & MG_BIT(2)) == 0) (void) 0; +} + +static uint32_t get_clock_rate(struct mg_tcpip_driver_xmc7_data *d) { + // see ETH0 -> NETWORK_CONFIG register + (void) d; + return 3; +} + +static bool mg_tcpip_driver_xmc7_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc7_data *d = + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data; + s_ifp = ifp; + + // enable controller, set RGMII mode + ETH0->CTL = MG_BIT(31) | 2; + + uint32_t cr = get_clock_rate(d); + // set NSP change, ignore RX FCS, data bus width, clock rate, Gigabit mode, + // frame length 1536, full duplex, speed + // TODO: enable Gigabit mode (bit 10) only if PHY uses Gigabit link + ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) | + ((cr & 7) << 18) | MG_BIT(10) | MG_BIT(8) | MG_BIT(4) | + MG_BIT(1) | MG_BIT(0); + + // config DMA settings: Force TX burst, Discard on Error, set RX buffer size + // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH + ETH0->DMA_CONFIG = + MG_BIT(26) | MG_BIT(24) | (0x18 << 16) | MG_BIT(10) | (3 << 8) | 4; + + // initialize descriptors + for (int i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = (uint32_t) s_rxbuf[i]; + if (i == ETH_DESC_CNT - 1) { + s_rxdesc[i][0] |= MG_BIT(1); // mark last descriptor + } + + s_txdesc[i][0] = (uint32_t) s_txbuf[i]; + s_txdesc[i][1] = MG_BIT(31); // OWN descriptor + if (i == ETH_DESC_CNT - 1) { + s_txdesc[i][1] |= MG_BIT(30); // mark last descriptor + } + } + ETH0->RECEIVE_Q_PTR = (uint32_t) s_rxdesc; + ETH0->TRANSMIT_Q_PTR = (uint32_t) s_txdesc; + + // disable other queues + ETH0->TRANSMIT_Q2_PTR = 1; + ETH0->TRANSMIT_Q1_PTR = 1; + ETH0->RECEIVE_Q2_PTR = 1; + ETH0->RECEIVE_Q1_PTR = 1; + + // enable interrupts (TX and RX complete) + ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1); + + // set MAC address + ETH0->SPEC_ADD1_BOTTOM = + ifp->mac[3] << 24 | ifp->mac[2] << 16 | ifp->mac[1] << 8 | ifp->mac[0]; + ETH0->SPEC_ADD1_TOP = ifp->mac[5] << 8 | ifp->mac[4]; + + // enable MDIO, TX, RX + ETH0->NETWORK_CONTROL = MG_BIT(4) | MG_BIT(3) | MG_BIT(2); + + // start transmission + ETH0->NETWORK_CONTROL |= MG_BIT(9); + + // init phy + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + + (void) d; + return true; +} + +static size_t mg_tcpip_driver_xmc7_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // Frame is too big + } else if (((s_txdesc[s_txno][1] & MG_BIT(31)) == 0)) { + ifp->nerr++; + MG_ERROR(("No free descriptors")); + len = 0; // All descriptors are busy, fail + } else { + memcpy(s_txbuf[s_txno], buf, len); + s_txdesc[s_txno][1] = (s_txno == ETH_DESC_CNT - 1 ? MG_BIT(30) : 0) | + MG_BIT(15) | len; // Last buffer and length + + ETH0->NETWORK_CONTROL |= MG_BIT(9); // enable transmission + if (++s_txno >= ETH_DESC_CNT) s_txno = 0; + } + + MG_DSB(); + ETH0->TRANSMIT_STATUS = ETH0->TRANSMIT_STATUS; + + return len; +} + +static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_xmc7_data *d = + (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {eth_read_phy, eth_write_phy}; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + MG_DEBUG(("Link is %uM %s-duplex", + speed == MG_PHY_SPEED_10M ? 10 : + (speed == MG_PHY_SPEED_100M ? 100 : 1000), + full_duplex ? "full" : "half")); + } + (void) d; + return up; +} + +void ETH_IRQHandler(void) { + uint32_t irq_status = ETH0->INT_STATUS; + if (irq_status & MG_BIT(1)) { + for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { + if (s_rxdesc[s_rxno][0] & MG_BIT(0)) { + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1); + //MG_INFO(("Receive complete: %ld bytes", len)); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control to DMA + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + } + + ETH0->INT_STATUS = irq_status; +} + +struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init, + mg_tcpip_driver_xmc7_tx, NULL, + mg_tcpip_driver_xmc7_up}; +#endif diff --git a/dist/mongoose/mongoose.h b/dist/mongoose/mongoose.h index 69d6ae615..16626c6bf 100644 --- a/dist/mongoose/mongoose.h +++ b/dist/mongoose/mongoose.h @@ -27,7 +27,7 @@ extern "C" { #endif -#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_custom.h +#define MG_ARCH_CUSTOM 0 // User creates its own mongoose_config.h #define MG_ARCH_UNIX 1 // Linux, BSD, Mac, ... #define MG_ARCH_WIN32 2 // Windows #define MG_ARCH_ESP32 3 // ESP32 @@ -48,30 +48,15 @@ extern "C" { #define MG_ARCH MG_ARCH_UNIX #elif defined(_WIN32) #define MG_ARCH MG_ARCH_WIN32 -#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR) -#define MG_ARCH MG_ARCH_ESP8266 -#elif defined(__ZEPHYR__) -#define MG_ARCH MG_ARCH_ZEPHYR -#elif defined(ESP_PLATFORM) -#define MG_ARCH MG_ARCH_ESP32 -#elif defined(FREERTOS_IP_H) -#define MG_ARCH MG_ARCH_FREERTOS -#define MG_ENABLE_FREERTOS_TCP 1 -#elif defined(AZURE_RTOS_THREADX) -#define MG_ARCH MG_ARCH_AZURERTOS -#elif defined(PICO_TARGET_NAME) -#define MG_ARCH MG_ARCH_RP2040 -#elif defined(__RTTHREAD__) -#define MG_ARCH MG_ARCH_RTTHREAD #endif #endif // !defined(MG_ARCH) #if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM) -#include "mongoose_custom.h" // keep this include +#include "mongoose_config.h" // keep this include #endif #if !defined(MG_ARCH) -#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..." +#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in your compiler" #endif // http://esr.ibiblio.org/?p=5095 @@ -200,7 +185,7 @@ extern "C" { #define calloc(a, b) mg_calloc(a, b) #define free(a) vPortFree(a) #define malloc(a) pvPortMalloc(a) -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib static inline void *mg_calloc(size_t cnt, size_t size) { @@ -302,7 +287,7 @@ extern uint32_t rt_time_get(void); #include "cmsis_os2.h" // keep this include #endif -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) #if defined(__ARMCC_VERSION) #define mode_t size_t @@ -522,8 +507,9 @@ typedef int socklen_t; #include #include -#include #include +#include +#include #include #include #include @@ -535,7 +521,7 @@ typedef int socklen_t; #define MG_PUTCHAR(x) printk("%c", x) #ifndef strdup -#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr) +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) #endif #define strerror(x) zsock_gai_strerror(x) @@ -833,17 +819,39 @@ struct timeval { #define MG_ENABLE_PROFILE 0 #endif +#ifndef MG_ENABLE_TCPIP_DRIVER_INIT // mg_mgr_init() will also initialize +#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for +#endif // Mongoose built-in network stack + +#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) +#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif +#ifndef MG_TCPIP_MASK +#define MG_TCPIP_MASK MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif +#ifndef MG_TCPIP_GW +#define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) +#endif + +#ifndef MG_SET_MAC_ADDRESS +#define MG_SET_MAC_ADDRESS(mac) +#endif +#ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS +#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 +#endif + + + + +// Describes an arbitrary chunk of memory struct mg_str { - const char *ptr; // Pointer to string data - size_t len; // String len + char *buf; // String data + size_t len; // String length }; -#define MG_C_STR(a) \ - { (a), sizeof(a) - 1 } - // Using macro to avoid shadowing C++ struct constructor, see #1298 #define mg_str(s) mg_str_s(s) @@ -864,7 +872,7 @@ bool mg_span(struct mg_str s, struct mg_str *a, struct mg_str *b, char delim); char *mg_hex(const void *buf, size_t len, char *dst); void mg_unhex(const char *buf, size_t len, unsigned char *to); unsigned long mg_unhexn(const char *s, size_t len); -bool mg_path_is_sane(const char *path); +bool mg_path_is_sane(const struct mg_str path); @@ -1060,6 +1068,8 @@ uint64_t mg_now(void); // Return milliseconds since Epoch (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \ ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255)) +#define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d)) + // For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip)) #define MG_U8P(ADDR) ((uint8_t *) (ADDR)) #define MG_IPADDR_PARTS(ADDR) \ @@ -1175,6 +1185,12 @@ typedef struct { void mg_sha1_init(mg_sha1_ctx *); void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); +// https://github.com/B-Con/crypto-algorithms +// Author: Brad Conte (brad AT bradconte.com) +// Disclaimer: This code is presented "as is" without any guarantees. +// Details: Defines the API for the corresponding SHA1 implementation. +// Copyright: public domain + @@ -1191,77 +1207,19 @@ void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len); void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *); void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, size_t datasz); -/****************************************************************************** - * - * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL - * - * This is a simple and straightforward implementation of the AES Rijndael - * 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus - * of this work was correctness & accuracy. It is written in 'C' without any - * particular focus upon optimization or speed. It should be endian (memory - * byte order) neutral since the few places that care are handled explicitly. - * - * This implementation of Rijndael was created by Steven M. Gibson of GRC.com. - * - * It is intended for general purpose use, but was written in support of GRC's - * reference implementation of the SQRL (Secure Quick Reliable Login) client. - * - * See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html - * - * NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE - * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. - * - *******************************************************************************/ - -#ifndef AES_HEADER -#define AES_HEADER - -/******************************************************************************/ -#define AES_DECRYPTION 1 // whether AES decryption is supported -/******************************************************************************/ +#ifndef TLS_X15519_H +#define TLS_X15519_H -#define MG_ENCRYPT 1 // specify whether we're encrypting -#define MG_DECRYPT 0 // or decrypting +#define X25519_BYTES 32 +extern const uint8_t X25519_BASE_POINT[X25519_BYTES]; -typedef unsigned char uchar; // add some convienent shorter types -typedef unsigned int uint; - -/****************************************************************************** - * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use - ******************************************************************************/ -void aes_init_keygen_tables(void); - -/****************************************************************************** - * AES_CONTEXT : cipher context / holds inter-call data - ******************************************************************************/ -typedef struct { - int mode; // 1 for Encryption, 0 for Decryption - int rounds; // keysize-based rounds count - uint32_t *rk; // pointer to current round key - uint32_t buf[68]; // key expansion buffer -} aes_context; - -/****************************************************************************** - * AES_SETKEY : called to expand the key for encryption or decryption - ******************************************************************************/ -int aes_setkey(aes_context *ctx, // pointer to context - int mode, // 1 or 0 for Encrypt/Decrypt - const uchar *key, // AES input key - uint keysize); // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) - // returns 0 for success +int mg_tls_x25519(uint8_t out[X25519_BYTES], const uint8_t scalar[X25519_BYTES], + const uint8_t x1[X25519_BYTES], int clamp); -/****************************************************************************** - * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data - ******************************************************************************/ -int aes_cipher(aes_context *ctx, // pointer to context - const uchar input[16], // 128-bit block to en/decipher - uchar output[16]); // 128-bit output result block - // returns 0 for success -#endif /* AES_HEADER */ +#endif /* TLS_X15519_H */ /****************************************************************************** * * THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL @@ -1285,151 +1243,30 @@ int aes_cipher(aes_context *ctx, // pointer to context * REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. * *******************************************************************************/ -#ifndef GCM_HEADER -#define GCM_HEADER +#ifndef TLS_AES128_H +#define TLS_AES128_H - -#define GCM_AUTH_FAILURE 0x55555555 // authentication failure +typedef unsigned char uchar; // add some convienent shorter types +typedef unsigned int uint; /****************************************************************************** - * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx + * AES_CONTEXT : cipher context / holds inter-call data ******************************************************************************/ typedef struct { - int mode; // cipher direction: encrypt/decrypt - uint64_t len; // cipher data length processed so far - uint64_t add_len; // total add data length - uint64_t HL[16]; // precalculated lo-half HTable - uint64_t HH[16]; // precalculated hi-half HTable - uchar base_ectr[16]; // first counter-mode cipher output for tag - uchar y[16]; // the current cipher-input IV|Counter value - uchar buf[16]; // buf working value - aes_context aes_ctx; // cipher context used -} gcm_context; - -/****************************************************************************** - * GCM_CONTEXT : MUST be called once before ANY use of this library - ******************************************************************************/ -int gcm_initialize(void); - -/****************************************************************************** - * GCM_SETKEY : sets the GCM (and AES) keying material for use - ******************************************************************************/ -int gcm_setkey(gcm_context *ctx, // caller-provided context ptr - const uchar *key, // pointer to cipher key - const uint keysize // size in bytes (must be 16, 24, 32 for - // 128, 192 or 256-bit keys respectively) -); // returns 0 for success - -/****************************************************************************** - * - * GCM_CRYPT_AND_TAG - * - * This either encrypts or decrypts the user-provided data and, either - * way, generates an authentication tag of the requested length. It must be - * called with a GCM context whose key has already been set with GCM_SETKEY. - * - * The user would typically call this explicitly to ENCRYPT a buffer of data - * and optional associated data, and produce its an authentication tag. - * - * To reverse the process the user would typically call the companion - * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided - * authentication tag. The GCM_AUTH_DECRYPT function calls this function - * to perform its decryption and tag generation, which it then compares. - * - ******************************************************************************/ -int gcm_crypt_and_tag( - gcm_context *ctx, // gcm context with key already setup - int mode, // cipher direction: MG_ENCRYPT (1) or MG_DECRYPT (0) - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - uchar *tag, // pointer to the tag to be generated - size_t tag_len); // byte length of the tag to be generated - -/****************************************************************************** - * - * GCM_AUTH_DECRYPT - * - * This DECRYPTS a user-provided data buffer with optional associated data. - * It then verifies a user-supplied authentication tag against the tag just - * re-created during decryption to verify that the data has not been altered. - * - * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption - * and authentication tag generation. - * - ******************************************************************************/ -int gcm_auth_decrypt( - gcm_context *ctx, // gcm context with key already setup - const uchar *iv, // pointer to the 12-byte initialization vector - size_t iv_len, // byte length if the IV. should always be 12 - const uchar *add, // pointer to the non-ciphered additional data - size_t add_len, // byte length of the additional AEAD data - const uchar *input, // pointer to the cipher data source - uchar *output, // pointer to the cipher data destination - size_t length, // byte length of the cipher data - const uchar *tag, // pointer to the tag to be authenticated - size_t tag_len); // byte length of the tag <= 16 - -/****************************************************************************** - * - * GCM_START - * - * Given a user-provided GCM context, this initializes it, sets the encryption - * mode, and preprocesses the initialization vector and additional AEAD data. - * - ******************************************************************************/ -int gcm_start( - gcm_context *ctx, // pointer to user-provided GCM context - int mode, // MG_ENCRYPT (1) or MG_DECRYPT (0) - const uchar *iv, // pointer to initialization vector - size_t iv_len, // IV length in bytes (should == 12) - const uchar *add, // pointer to additional AEAD data (NULL if none) - size_t add_len); // length of additional AEAD data (bytes) + int mode; // 1 for Encryption, 0 for Decryption + int rounds; // keysize-based rounds count + uint32_t *rk; // pointer to current round key + uint32_t buf[68]; // key expansion buffer +} aes_context; -/****************************************************************************** - * - * GCM_UPDATE - * - * This is called once or more to process bulk plaintext or ciphertext data. - * We give this some number of bytes of input and it returns the same number - * of output bytes. If called multiple times (which is fine) all but the final - * invocation MUST be called with length mod 16 == 0. (Only the final call can - * have a partial block length of < 128 bits.) - * - ******************************************************************************/ -int gcm_update(gcm_context *ctx, // pointer to user-provided GCM context - size_t length, // length, in bytes, of data to process - const uchar *input, // pointer to source data - uchar *output); // pointer to destination data -/****************************************************************************** - * - * GCM_FINISH - * - * This is called once after all calls to GCM_UPDATE to finalize the GCM. - * It performs the final GHASH to produce the resulting authentication TAG. - * - ******************************************************************************/ -int gcm_finish(gcm_context *ctx, // pointer to user-provided GCM context - uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 - size_t tag_len); // length, in bytes, of the tag-receiving buf +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure /****************************************************************************** - * - * GCM_ZERO_CTX - * - * The GCM context contains both the GCM context and the AES context. - * This includes keying and key-related material which is security- - * sensitive, so it MUST be zeroed after use. This function does that. - * + * GCM_CONTEXT : MUST be called once before ANY use of this library ******************************************************************************/ -void gcm_zero_ctx(gcm_context *ctx); +int mg_gcm_initialize(void); -#endif /* GCM_HEADER */ // // aes-gcm.h // MKo @@ -1437,67 +1274,64 @@ void gcm_zero_ctx(gcm_context *ctx); // Created by Markus Kosmal on 20/11/14. // // +int mg_aes_gcm_encrypt(unsigned char *output, const unsigned char *input, + size_t input_length, const unsigned char *key, + const size_t key_len, const unsigned char *iv, + const size_t iv_len, unsigned char *aead, + size_t aead_len, unsigned char *tag, + const size_t tag_len); -#ifndef mko_aes_gcm_h -#define mko_aes_gcm_h - -int aes_gcm_encrypt(unsigned char *output, const unsigned char *input, - size_t input_length, const unsigned char *key, - const size_t key_len, const unsigned char *iv, - const size_t iv_len, unsigned char *aead, size_t aead_len, - unsigned char *tag, const size_t tag_len); +int mg_aes_gcm_decrypt(unsigned char *output, const unsigned char *input, + size_t input_length, const unsigned char *key, + const size_t key_len, const unsigned char *iv, + const size_t iv_len); -int aes_gcm_decrypt(unsigned char *output, const unsigned char *input, - size_t input_length, const unsigned char *key, - const size_t key_len, const unsigned char *iv, - const size_t iv_len); - -#endif +#endif /* TLS_AES128_H */ // End of aes128 PD -#define uECC_SUPPORTS_secp256r1 1 +#define MG_UECC_SUPPORTS_secp256r1 1 /* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. */ #ifndef _UECC_H_ #define _UECC_H_ /* Platform selection options. -If uECC_PLATFORM is not defined, the code will try to guess it based on compiler -macros. Possible values for uECC_PLATFORM are defined below: */ -#define uECC_arch_other 0 -#define uECC_x86 1 -#define uECC_x86_64 2 -#define uECC_arm 3 -#define uECC_arm_thumb 4 -#define uECC_arm_thumb2 5 -#define uECC_arm64 6 -#define uECC_avr 7 - -/* If desired, you can define uECC_WORD_SIZE as appropriate for your platform -(1, 4, or 8 bytes). If uECC_WORD_SIZE is not explicitly defined then it will be -automatically set based on your platform. */ +If MG_UECC_PLATFORM is not defined, the code will try to guess it based on +compiler macros. Possible values for MG_UECC_PLATFORM are defined below: */ +#define mg_uecc_arch_other 0 +#define mg_uecc_x86 1 +#define mg_uecc_x86_64 2 +#define mg_uecc_arm 3 +#define mg_uecc_arm_thumb 4 +#define mg_uecc_arm_thumb2 5 +#define mg_uecc_arm64 6 +#define mg_uecc_avr 7 + +/* If desired, you can define MG_UECC_WORD_SIZE as appropriate for your platform +(1, 4, or 8 bytes). If MG_UECC_WORD_SIZE is not explicitly defined then it will +be automatically set based on your platform. */ /* Optimization level; trade speed for code size. Larger values produce code that is faster but larger. Currently supported values are 0 - 4; 0 is unusably slow for most applications. Optimization level 4 currently only has an effect ARM platforms where more than one curve is enabled. */ -#ifndef uECC_OPTIMIZATION_LEVEL -#define uECC_OPTIMIZATION_LEVEL 2 +#ifndef MG_UECC_OPTIMIZATION_LEVEL +#define MG_UECC_OPTIMIZATION_LEVEL 2 #endif -/* uECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a +/* MG_UECC_SQUARE_FUNC - If enabled (defined as nonzero), this will cause a specific function to be used for (scalar) squaring instead of the generic multiplication function. This can make things faster somewhat faster, but increases the code size. */ -#ifndef uECC_SQUARE_FUNC -#define uECC_SQUARE_FUNC 0 +#ifndef MG_UECC_SQUARE_FUNC +#define MG_UECC_SQUARE_FUNC 0 #endif -/* uECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will +/* MG_UECC_VLI_NATIVE_LITTLE_ENDIAN - If enabled (defined as nonzero), this will switch to native little-endian format for *all* arrays passed in and out of the public API. This includes public and private keys, shared secrets, signatures and message hashes. Using this switch reduces the amount of call stack memory @@ -1506,111 +1340,112 @@ will *only* work on native little-endian processors and it will treat the uint8_t arrays passed into the public API as word arrays, therefore requiring the provided byte arrays to be word aligned on architectures that do not support unaligned accesses. IMPORTANT: Keys and signatures generated with -uECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures -generated with uECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same +MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=1 are incompatible with keys and signatures +generated with MG_UECC_VLI_NATIVE_LITTLE_ENDIAN=0; all parties must use the same endianness. */ -#ifndef uECC_VLI_NATIVE_LITTLE_ENDIAN -#define uECC_VLI_NATIVE_LITTLE_ENDIAN 0 +#ifndef MG_UECC_VLI_NATIVE_LITTLE_ENDIAN +#define MG_UECC_VLI_NATIVE_LITTLE_ENDIAN 0 #endif /* Curve support selection. Set to 0 to remove that curve. */ -#ifndef uECC_SUPPORTS_secp160r1 -#define uECC_SUPPORTS_secp160r1 0 +#ifndef MG_UECC_SUPPORTS_secp160r1 +#define MG_UECC_SUPPORTS_secp160r1 0 #endif -#ifndef uECC_SUPPORTS_secp192r1 -#define uECC_SUPPORTS_secp192r1 0 +#ifndef MG_UECC_SUPPORTS_secp192r1 +#define MG_UECC_SUPPORTS_secp192r1 0 #endif -#ifndef uECC_SUPPORTS_secp224r1 -#define uECC_SUPPORTS_secp224r1 0 +#ifndef MG_UECC_SUPPORTS_secp224r1 +#define MG_UECC_SUPPORTS_secp224r1 0 #endif -#ifndef uECC_SUPPORTS_secp256r1 -#define uECC_SUPPORTS_secp256r1 1 +#ifndef MG_UECC_SUPPORTS_secp256r1 +#define MG_UECC_SUPPORTS_secp256r1 1 #endif -#ifndef uECC_SUPPORTS_secp256k1 -#define uECC_SUPPORTS_secp256k1 0 +#ifndef MG_UECC_SUPPORTS_secp256k1 +#define MG_UECC_SUPPORTS_secp256k1 0 #endif /* Specifies whether compressed point format is supported. Set to 0 to disable point compression/decompression functions. */ -#ifndef uECC_SUPPORT_COMPRESSED_POINT -#define uECC_SUPPORT_COMPRESSED_POINT 1 +#ifndef MG_UECC_SUPPORT_COMPRESSED_POINT +#define MG_UECC_SUPPORT_COMPRESSED_POINT 1 #endif -struct uECC_Curve_t; -typedef const struct uECC_Curve_t *uECC_Curve; +struct MG_UECC_Curve_t; +typedef const struct MG_UECC_Curve_t *MG_UECC_Curve; #ifdef __cplusplus extern "C" { #endif -#if uECC_SUPPORTS_secp160r1 -uECC_Curve uECC_secp160r1(void); +#if MG_UECC_SUPPORTS_secp160r1 +MG_UECC_Curve mg_uecc_secp160r1(void); #endif -#if uECC_SUPPORTS_secp192r1 -uECC_Curve uECC_secp192r1(void); +#if MG_UECC_SUPPORTS_secp192r1 +MG_UECC_Curve mg_uecc_secp192r1(void); #endif -#if uECC_SUPPORTS_secp224r1 -uECC_Curve uECC_secp224r1(void); +#if MG_UECC_SUPPORTS_secp224r1 +MG_UECC_Curve mg_uecc_secp224r1(void); #endif -#if uECC_SUPPORTS_secp256r1 -uECC_Curve uECC_secp256r1(void); +#if MG_UECC_SUPPORTS_secp256r1 +MG_UECC_Curve mg_uecc_secp256r1(void); #endif -#if uECC_SUPPORTS_secp256k1 -uECC_Curve uECC_secp256k1(void); +#if MG_UECC_SUPPORTS_secp256k1 +MG_UECC_Curve mg_uecc_secp256k1(void); #endif -/* uECC_RNG_Function type +/* MG_UECC_RNG_Function type The RNG function should fill 'size' random bytes into 'dest'. It should return 1 if 'dest' was filled with random data, or 0 if the random data could not be generated. The filled-in values should be either truly random, or from a cryptographically-secure PRNG. -A correctly functioning RNG function must be set (using uECC_set_rng()) before -calling uECC_make_key() or uECC_sign(). +A correctly functioning RNG function must be set (using mg_uecc_set_rng()) +before calling mg_uecc_make_key() or mg_uecc_sign(). Setting a correctly functioning RNG function improves the resistance to -side-channel attacks for uECC_shared_secret() and uECC_sign_deterministic(). +side-channel attacks for mg_uecc_shared_secret() and +mg_uecc_sign_deterministic(). A correct RNG function is set by default when building for Windows, Linux, or OS X. If you are building on another POSIX-compliant system that supports -/dev/random or /dev/urandom, you can define uECC_POSIX to use the predefined +/dev/random or /dev/urandom, you can define MG_UECC_POSIX to use the predefined RNG. For embedded platforms there is no predefined RNG function; you must provide your own. */ -typedef int (*uECC_RNG_Function)(uint8_t *dest, unsigned size); +typedef int (*MG_UECC_RNG_Function)(uint8_t *dest, unsigned size); -/* uECC_set_rng() function. +/* mg_uecc_set_rng() function. Set the function that will be used to generate random bytes. The RNG function should return 1 if the random data was generated, or 0 if the random data could not be generated. On platforms where there is no predefined RNG function (eg embedded platforms), -this must be called before uECC_make_key() or uECC_sign() are used. +this must be called before mg_uecc_make_key() or mg_uecc_sign() are used. Inputs: rng_function - The function that will be used to generate random bytes. */ -void uECC_set_rng(uECC_RNG_Function rng_function); +void mg_uecc_set_rng(MG_UECC_RNG_Function rng_function); -/* uECC_get_rng() function. +/* mg_uecc_get_rng() function. Returns the function that will be used to generate random bytes. */ -uECC_RNG_Function uECC_get_rng(void); +MG_UECC_RNG_Function mg_uecc_get_rng(void); -/* uECC_curve_private_key_size() function. +/* mg_uecc_curve_private_key_size() function. Returns the size of a private key for the curve in bytes. */ -int uECC_curve_private_key_size(uECC_Curve curve); +int mg_uecc_curve_private_key_size(MG_UECC_Curve curve); -/* uECC_curve_public_key_size() function. +/* mg_uecc_curve_public_key_size() function. Returns the size of a public key for the curve in bytes. */ -int uECC_curve_public_key_size(uECC_Curve curve); +int mg_uecc_curve_public_key_size(MG_UECC_Curve curve); -/* uECC_make_key() function. +/* mg_uecc_make_key() function. Create a public/private key pair. Outputs: @@ -1627,14 +1462,15 @@ being non-zero). Returns 1 if the key pair was generated successfully, 0 if an error occurred. */ -int uECC_make_key(uint8_t *public_key, uint8_t *private_key, uECC_Curve curve); +int mg_uecc_make_key(uint8_t *public_key, uint8_t *private_key, + MG_UECC_Curve curve); -/* uECC_shared_secret() function. +/* mg_uecc_shared_secret() function. Compute a shared secret given your secret key and someone else's public key. If the public key is not from a trusted source and has not been previously -verified, you should verify it first using uECC_valid_public_key(). Note: It is -recommended that you hash the result of uECC_shared_secret() before using it for -symmetric encryption or HMAC. +verified, you should verify it first using mg_uecc_valid_public_key(). Note: It +is recommended that you hash the result of mg_uecc_shared_secret() before using +it for symmetric encryption or HMAC. Inputs: public_key - The public key of the remote party. @@ -1648,11 +1484,11 @@ size as the curve size; for example, if the curve is secp256r1, secret must be Returns 1 if the shared secret was generated successfully, 0 if an error occurred. */ -int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key, - uint8_t *secret, uECC_Curve curve); +int mg_uecc_shared_secret(const uint8_t *public_key, const uint8_t *private_key, + uint8_t *secret, MG_UECC_Curve curve); -#if uECC_SUPPORT_COMPRESSED_POINT -/* uECC_compress() function. +#if MG_UECC_SUPPORT_COMPRESSED_POINT +/* mg_uecc_compress() function. Compress a public key. Inputs: @@ -1663,10 +1499,10 @@ Compress a public key. least (curve size + 1) bytes long; for example, if the curve is secp256r1, compressed must be 33 bytes long. */ -void uECC_compress(const uint8_t *public_key, uint8_t *compressed, - uECC_Curve curve); +void mg_uecc_compress(const uint8_t *public_key, uint8_t *compressed, + MG_UECC_Curve curve); -/* uECC_decompress() function. +/* mg_uecc_decompress() function. Decompress a compressed public key. Inputs: @@ -1675,11 +1511,11 @@ Decompress a compressed public key. Outputs: public_key - Will be filled in with the decompressed public key. */ -void uECC_decompress(const uint8_t *compressed, uint8_t *public_key, - uECC_Curve curve); -#endif /* uECC_SUPPORT_COMPRESSED_POINT */ +void mg_uecc_decompress(const uint8_t *compressed, uint8_t *public_key, + MG_UECC_Curve curve); +#endif /* MG_UECC_SUPPORT_COMPRESSED_POINT */ -/* uECC_valid_public_key() function. +/* mg_uecc_valid_public_key() function. Check to see if a public key is valid. Note that you are not required to check for a valid public key before using any @@ -1691,9 +1527,9 @@ a shared secret or verifying a signature using an invalid public key. Returns 1 if the public key is valid, 0 if it is invalid. */ -int uECC_valid_public_key(const uint8_t *public_key, uECC_Curve curve); +int mg_uecc_valid_public_key(const uint8_t *public_key, MG_UECC_Curve curve); -/* uECC_compute_public_key() function. +/* mg_uecc_compute_public_key() function. Compute the corresponding public key for a private key. Inputs: @@ -1704,10 +1540,10 @@ Compute the corresponding public key for a private key. Returns 1 if the key was computed successfully, 0 if an error occurred. */ -int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, - uECC_Curve curve); +int mg_uecc_compute_public_key(const uint8_t *private_key, uint8_t *public_key, + MG_UECC_Curve curve); -/* uECC_sign() function. +/* mg_uecc_sign() function. Generate an ECDSA signature for a given hash value. Usage: Compute a hash of the data you wish to sign (SHA-2 is recommended) and @@ -1725,37 +1561,37 @@ bytes long. Returns 1 if the signature generated successfully, 0 if an error occurred. */ -int uECC_sign(const uint8_t *private_key, const uint8_t *message_hash, - unsigned hash_size, uint8_t *signature, uECC_Curve curve); +int mg_uecc_sign(const uint8_t *private_key, const uint8_t *message_hash, + unsigned hash_size, uint8_t *signature, MG_UECC_Curve curve); -/* uECC_HashContext structure. -This is used to pass in an arbitrary hash function to uECC_sign_deterministic(). -The structure will be used for multiple hash computations; each time a new hash -is computed, init_hash() will be called, followed by one or more calls to -update_hash(), and finally a call to finish_hash() to produce the resulting -hash. +/* MG_UECC_HashContext structure. +This is used to pass in an arbitrary hash function to +mg_uecc_sign_deterministic(). The structure will be used for multiple hash +computations; each time a new hash is computed, init_hash() will be called, +followed by one or more calls to update_hash(), and finally a call to +finish_hash() to produce the resulting hash. -The intention is that you will create a structure that includes uECC_HashContext -followed by any hash-specific data. For example: +The intention is that you will create a structure that includes +MG_UECC_HashContext followed by any hash-specific data. For example: typedef struct SHA256_HashContext { - uECC_HashContext uECC; + MG_UECC_HashContext uECC; SHA256_CTX ctx; } SHA256_HashContext; -void init_SHA256(uECC_HashContext *base) { +void init_SHA256(MG_UECC_HashContext *base) { SHA256_HashContext *context = (SHA256_HashContext *)base; SHA256_Init(&context->ctx); } -void update_SHA256(uECC_HashContext *base, +void update_SHA256(MG_UECC_HashContext *base, const uint8_t *message, unsigned message_size) { SHA256_HashContext *context = (SHA256_HashContext *)base; SHA256_Update(&context->ctx, message, message_size); } -void finish_SHA256(uECC_HashContext *base, uint8_t *hash_result) { +void finish_SHA256(MG_UECC_HashContext *base, uint8_t *hash_result) { SHA256_HashContext *context = (SHA256_HashContext *)base; SHA256_Final(hash_result, &context->ctx); } @@ -1764,14 +1600,14 @@ void finish_SHA256(uECC_HashContext *base, uint8_t *hash_result) { { uint8_t tmp[32 + 32 + 64]; SHA256_HashContext ctx = {{&init_SHA256, &update_SHA256, &finish_SHA256, 64, -32, tmp}}; uECC_sign_deterministic(key, message_hash, &ctx.uECC, signature); +32, tmp}}; mg_uecc_sign_deterministic(key, message_hash, &ctx.uECC, signature); } */ -typedef struct uECC_HashContext { - void (*init_hash)(const struct uECC_HashContext *context); - void (*update_hash)(const struct uECC_HashContext *context, +typedef struct MG_UECC_HashContext { + void (*init_hash)(const struct MG_UECC_HashContext *context); + void (*update_hash)(const struct MG_UECC_HashContext *context, const uint8_t *message, unsigned message_size); - void (*finish_hash)(const struct uECC_HashContext *context, + void (*finish_hash)(const struct MG_UECC_HashContext *context, uint8_t *hash_result); unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */ @@ -1779,11 +1615,11 @@ typedef struct uECC_HashContext { result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */ uint8_t *tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */ -} uECC_HashContext; +} MG_UECC_HashContext; -/* uECC_sign_deterministic() function. +/* mg_uecc_sign_deterministic() function. Generate an ECDSA signature for a given hash value, using a deterministic -algorithm (see RFC 6979). You do not need to set the RNG using uECC_set_rng() +algorithm (see RFC 6979). You do not need to set the RNG using mg_uecc_set_rng() before calling this function; however, if the RNG is defined it will improve resistance to side-channel attacks. @@ -1803,12 +1639,12 @@ used by hash_context. Returns 1 if the signature generated successfully, 0 if an error occurred. */ -int uECC_sign_deterministic(const uint8_t *private_key, - const uint8_t *message_hash, unsigned hash_size, - const uECC_HashContext *hash_context, - uint8_t *signature, uECC_Curve curve); +int mg_uecc_sign_deterministic(const uint8_t *private_key, + const uint8_t *message_hash, unsigned hash_size, + const MG_UECC_HashContext *hash_context, + uint8_t *signature, MG_UECC_Curve curve); -/* uECC_verify() function. +/* mg_uecc_verify() function. Verify an ECDSA signature. Usage: Compute the hash of the signed data using the same hash as the signer and @@ -1823,8 +1659,9 @@ values (r and s). Returns 1 if the signature is valid, 0 if it is invalid. */ -int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, - unsigned hash_size, const uint8_t *signature, uECC_Curve curve); +int mg_uecc_verify(const uint8_t *public_key, const uint8_t *message_hash, + unsigned hash_size, const uint8_t *signature, + MG_UECC_Curve curve); #ifdef __cplusplus } /* end of extern "C" */ @@ -1837,153 +1674,162 @@ int uECC_verify(const uint8_t *public_key, const uint8_t *message_hash, #ifndef _UECC_VLI_H_ #define _UECC_VLI_H_ -// -// +// +// /* Functions for raw large-integer manipulation. These are only available - if uECC.c is compiled with uECC_ENABLE_VLI_API defined to 1. */ -#ifndef uECC_ENABLE_VLI_API -#define uECC_ENABLE_VLI_API 0 + if uECC.c is compiled with MG_UECC_ENABLE_VLI_API defined to 1. */ +#ifndef MG_UECC_ENABLE_VLI_API +#define MG_UECC_ENABLE_VLI_API 0 #endif #ifdef __cplusplus extern "C" { #endif -#if uECC_ENABLE_VLI_API +#if MG_UECC_ENABLE_VLI_API -void uECC_vli_clear(uECC_word_t *vli, wordcount_t num_words); +void mg_uecc_vli_clear(mg_uecc_word_t *vli, wordcount_t num_words); /* Constant-time comparison to zero - secure way to compare long integers */ /* Returns 1 if vli == 0, 0 otherwise. */ -uECC_word_t uECC_vli_isZero(const uECC_word_t *vli, wordcount_t num_words); +mg_uecc_word_t mg_uecc_vli_isZero(const mg_uecc_word_t *vli, + wordcount_t num_words); /* Returns nonzero if bit 'bit' of vli is set. */ -uECC_word_t uECC_vli_testBit(const uECC_word_t *vli, bitcount_t bit); +mg_uecc_word_t mg_uecc_vli_testBit(const mg_uecc_word_t *vli, bitcount_t bit); /* Counts the number of bits required to represent vli. */ -bitcount_t uECC_vli_numBits(const uECC_word_t *vli, - const wordcount_t max_words); +bitcount_t mg_uecc_vli_numBits(const mg_uecc_word_t *vli, + const wordcount_t max_words); /* Sets dest = src. */ -void uECC_vli_set(uECC_word_t *dest, const uECC_word_t *src, - wordcount_t num_words); +void mg_uecc_vli_set(mg_uecc_word_t *dest, const mg_uecc_word_t *src, + wordcount_t num_words); /* Constant-time comparison function - secure way to compare long integers */ /* Returns one if left == right, zero otherwise */ -uECC_word_t uECC_vli_equal(const uECC_word_t *left, const uECC_word_t *right, - wordcount_t num_words); +mg_uecc_word_t mg_uecc_vli_equal(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); /* Constant-time comparison function - secure way to compare long integers */ /* Returns sign of left - right, in constant time. */ -cmpresult_t uECC_vli_cmp(const uECC_word_t *left, const uECC_word_t *right, - wordcount_t num_words); +cmpresult_t mg_uecc_vli_cmp(const mg_uecc_word_t *left, + const mg_uecc_word_t *right, wordcount_t num_words); /* Computes vli = vli >> 1. */ -void uECC_vli_rshift1(uECC_word_t *vli, wordcount_t num_words); +void mg_uecc_vli_rshift1(mg_uecc_word_t *vli, wordcount_t num_words); /* Computes result = left + right, returning carry. Can modify in place. */ -uECC_word_t uECC_vli_add(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); +mg_uecc_word_t mg_uecc_vli_add(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); /* Computes result = left - right, returning borrow. Can modify in place. */ -uECC_word_t uECC_vli_sub(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); +mg_uecc_word_t mg_uecc_vli_sub(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, + wordcount_t num_words); /* Computes result = left * right. Result must be 2 * num_words long. */ -void uECC_vli_mult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, wordcount_t num_words); +void mg_uecc_vli_mult(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, wordcount_t num_words); /* Computes result = left^2. Result must be 2 * num_words long. */ -void uECC_vli_square(uECC_word_t *result, const uECC_word_t *left, - wordcount_t num_words); +void mg_uecc_vli_square(mg_uecc_word_t *result, const mg_uecc_word_t *left, + wordcount_t num_words); /* Computes result = (left + right) % mod. Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modAdd(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); +void mg_uecc_vli_modAdd(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); /* Computes result = (left - right) % mod. Assumes that left < mod and right < mod, and that result does not overlap mod. */ -void uECC_vli_modSub(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); +void mg_uecc_vli_modSub(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); /* Computes result = product % mod, where product is 2N words long. Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_mmod(uECC_word_t *result, uECC_word_t *product, - const uECC_word_t *mod, wordcount_t num_words); +void mg_uecc_vli_mmod(mg_uecc_word_t *result, mg_uecc_word_t *product, + const mg_uecc_word_t *mod, wordcount_t num_words); /* Calculates result = product (mod curve->p), where product is up to 2 * curve->num_words long. */ -void uECC_vli_mmod_fast(uECC_word_t *result, uECC_word_t *product, - uECC_Curve curve); +void mg_uecc_vli_mmod_fast(mg_uecc_word_t *result, mg_uecc_word_t *product, + MG_UECC_Curve curve); /* Computes result = (left * right) % mod. Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modMult(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, const uECC_word_t *mod, - wordcount_t num_words); +void mg_uecc_vli_modMult(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *right, const mg_uecc_word_t *mod, + wordcount_t num_words); /* Computes result = (left * right) % curve->p. */ -void uECC_vli_modMult_fast(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *right, uECC_Curve curve); +void mg_uecc_vli_modMult_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + const mg_uecc_word_t *right, MG_UECC_Curve curve); /* Computes result = left^2 % mod. Currently only designed to work for mod == curve->p or curve_n. */ -void uECC_vli_modSquare(uECC_word_t *result, const uECC_word_t *left, - const uECC_word_t *mod, wordcount_t num_words); +void mg_uecc_vli_modSquare(mg_uecc_word_t *result, const mg_uecc_word_t *left, + const mg_uecc_word_t *mod, wordcount_t num_words); /* Computes result = left^2 % curve->p. */ -void uECC_vli_modSquare_fast(uECC_word_t *result, const uECC_word_t *left, - uECC_Curve curve); +void mg_uecc_vli_modSquare_fast(mg_uecc_word_t *result, + const mg_uecc_word_t *left, + MG_UECC_Curve curve); /* Computes result = (1 / input) % mod.*/ -void uECC_vli_modInv(uECC_word_t *result, const uECC_word_t *input, - const uECC_word_t *mod, wordcount_t num_words); +void mg_uecc_vli_modInv(mg_uecc_word_t *result, const mg_uecc_word_t *input, + const mg_uecc_word_t *mod, wordcount_t num_words); -#if uECC_SUPPORT_COMPRESSED_POINT +#if MG_UECC_SUPPORT_COMPRESSED_POINT /* Calculates a = sqrt(a) (mod curve->p) */ -void uECC_vli_mod_sqrt(uECC_word_t *a, uECC_Curve curve); +void mg_uecc_vli_mod_sqrt(mg_uecc_word_t *a, MG_UECC_Curve curve); #endif /* Converts an integer in uECC native format to big-endian bytes. */ -void uECC_vli_nativeToBytes(uint8_t *bytes, int num_bytes, - const uECC_word_t *native); +void mg_uecc_vli_nativeToBytes(uint8_t *bytes, int num_bytes, + const mg_uecc_word_t *native); /* Converts big-endian bytes to an integer in uECC native format. */ -void uECC_vli_bytesToNative(uECC_word_t *native, const uint8_t *bytes, - int num_bytes); +void mg_uecc_vli_bytesToNative(mg_uecc_word_t *native, const uint8_t *bytes, + int num_bytes); -unsigned uECC_curve_num_words(uECC_Curve curve); -unsigned uECC_curve_num_bytes(uECC_Curve curve); -unsigned uECC_curve_num_bits(uECC_Curve curve); -unsigned uECC_curve_num_n_words(uECC_Curve curve); -unsigned uECC_curve_num_n_bytes(uECC_Curve curve); -unsigned uECC_curve_num_n_bits(uECC_Curve curve); +unsigned mg_uecc_curve_num_words(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_bytes(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_bits(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_words(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_bytes(MG_UECC_Curve curve); +unsigned mg_uecc_curve_num_n_bits(MG_UECC_Curve curve); -const uECC_word_t *uECC_curve_p(uECC_Curve curve); -const uECC_word_t *uECC_curve_n(uECC_Curve curve); -const uECC_word_t *uECC_curve_G(uECC_Curve curve); -const uECC_word_t *uECC_curve_b(uECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_p(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_n(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_G(MG_UECC_Curve curve); +const mg_uecc_word_t *mg_uecc_curve_b(MG_UECC_Curve curve); -int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve); +int mg_uecc_valid_point(const mg_uecc_word_t *point, MG_UECC_Curve curve); /* Multiplies a point by a scalar. Points are represented by the X coordinate followed by the Y coordinate in the same array, both coordinates are curve->num_words long. Note that scalar must be curve->num_n_words long (NOT curve->num_words). */ -void uECC_point_mult(uECC_word_t *result, const uECC_word_t *point, - const uECC_word_t *scalar, uECC_Curve curve); +void mg_uecc_point_mult(mg_uecc_word_t *result, const mg_uecc_word_t *point, + const mg_uecc_word_t *scalar, MG_UECC_Curve curve); /* Generates a random integer in the range 0 < random < top. Both random and top have num_words words. */ -int uECC_generate_random_int(uECC_word_t *random, const uECC_word_t *top, - wordcount_t num_words); +int mg_uecc_generate_random_int(mg_uecc_word_t *random, + const mg_uecc_word_t *top, + wordcount_t num_words); -#endif /* uECC_ENABLE_VLI_API */ +#endif /* MG_UECC_ENABLE_VLI_API */ #ifdef __cplusplus } /* end of extern "C" */ @@ -1996,101 +1842,103 @@ int uECC_generate_random_int(uECC_word_t *random, const uECC_word_t *top, #ifndef _UECC_TYPES_H_ #define _UECC_TYPES_H_ -#ifndef uECC_PLATFORM +#ifndef MG_UECC_PLATFORM #if defined(__AVR__) && __AVR__ -#define uECC_PLATFORM uECC_avr +#define MG_UECC_PLATFORM mg_uecc_avr #elif defined(__thumb2__) || \ defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ -#define uECC_PLATFORM uECC_arm_thumb2 +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2 #elif defined(__thumb__) -#define uECC_PLATFORM uECC_arm_thumb +#define MG_UECC_PLATFORM mg_uecc_arm_thumb #elif defined(__arm__) || defined(_M_ARM) -#define uECC_PLATFORM uECC_arm +#define MG_UECC_PLATFORM mg_uecc_arm #elif defined(__aarch64__) -#define uECC_PLATFORM uECC_arm64 +#define MG_UECC_PLATFORM mg_uecc_arm64 #elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \ defined(__I86__) -#define uECC_PLATFORM uECC_x86 +#define MG_UECC_PLATFORM mg_uecc_x86 #elif defined(__amd64__) || defined(_M_X64) -#define uECC_PLATFORM uECC_x86_64 +#define MG_UECC_PLATFORM mg_uecc_x86_64 #else -#define uECC_PLATFORM uECC_arch_other +#define MG_UECC_PLATFORM mg_uecc_arch_other #endif #endif -#ifndef uECC_ARM_USE_UMAAL -#if (uECC_PLATFORM == uECC_arm) && (__ARM_ARCH >= 6) -#define uECC_ARM_USE_UMAAL 1 -#elif (uECC_PLATFORM == uECC_arm_thumb2) && (__ARM_ARCH >= 6) && \ +#ifndef MG_UECC_ARM_USE_UMAAL +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6) +#define MG_UECC_ARM_USE_UMAAL 1 +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \ (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__) -#define uECC_ARM_USE_UMAAL 1 +#define MG_UECC_ARM_USE_UMAAL 1 #else -#define uECC_ARM_USE_UMAAL 0 +#define MG_UECC_ARM_USE_UMAAL 0 #endif #endif -#ifndef uECC_WORD_SIZE -#if uECC_PLATFORM == uECC_avr -#define uECC_WORD_SIZE 1 -#elif (uECC_PLATFORM == uECC_x86_64 || uECC_PLATFORM == uECC_arm64) -#define uECC_WORD_SIZE 8 +#ifndef MG_UECC_WORD_SIZE +#if MG_UECC_PLATFORM == mg_uecc_avr +#define MG_UECC_WORD_SIZE 1 +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64) +#define MG_UECC_WORD_SIZE 8 #else -#define uECC_WORD_SIZE 4 +#define MG_UECC_WORD_SIZE 4 #endif #endif -#if (uECC_WORD_SIZE != 1) && (uECC_WORD_SIZE != 4) && (uECC_WORD_SIZE != 8) -#error "Unsupported value for uECC_WORD_SIZE" +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \ + (MG_UECC_WORD_SIZE != 8) +#error "Unsupported value for MG_UECC_WORD_SIZE" #endif -#if ((uECC_PLATFORM == uECC_avr) && (uECC_WORD_SIZE != 1)) -#pragma message("uECC_WORD_SIZE must be 1 for AVR") -#undef uECC_WORD_SIZE -#define uECC_WORD_SIZE 1 +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1)) +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 1 #endif -#if ((uECC_PLATFORM == uECC_arm || uECC_PLATFORM == uECC_arm_thumb || \ - uECC_PLATFORM == uECC_arm_thumb2) && \ - (uECC_WORD_SIZE != 4)) -#pragma message("uECC_WORD_SIZE must be 4 for ARM") -#undef uECC_WORD_SIZE -#define uECC_WORD_SIZE 4 +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \ + (MG_UECC_WORD_SIZE != 4)) +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 4 #endif typedef int8_t wordcount_t; typedef int16_t bitcount_t; typedef int8_t cmpresult_t; -#if (uECC_WORD_SIZE == 1) +#if (MG_UECC_WORD_SIZE == 1) -typedef uint8_t uECC_word_t; -typedef uint16_t uECC_dword_t; +typedef uint8_t mg_uecc_word_t; +typedef uint16_t mg_uecc_dword_t; #define HIGH_BIT_SET 0x80 -#define uECC_WORD_BITS 8 -#define uECC_WORD_BITS_SHIFT 3 -#define uECC_WORD_BITS_MASK 0x07 +#define MG_UECC_WORD_BITS 8 +#define MG_UECC_WORD_BITS_SHIFT 3 +#define MG_UECC_WORD_BITS_MASK 0x07 -#elif (uECC_WORD_SIZE == 4) +#elif (MG_UECC_WORD_SIZE == 4) -typedef uint32_t uECC_word_t; -typedef uint64_t uECC_dword_t; +typedef uint32_t mg_uecc_word_t; +typedef uint64_t mg_uecc_dword_t; #define HIGH_BIT_SET 0x80000000 -#define uECC_WORD_BITS 32 -#define uECC_WORD_BITS_SHIFT 5 -#define uECC_WORD_BITS_MASK 0x01F +#define MG_UECC_WORD_BITS 32 +#define MG_UECC_WORD_BITS_SHIFT 5 +#define MG_UECC_WORD_BITS_MASK 0x01F -#elif (uECC_WORD_SIZE == 8) +#elif (MG_UECC_WORD_SIZE == 8) -typedef uint64_t uECC_word_t; +typedef uint64_t mg_uecc_word_t; #define HIGH_BIT_SET 0x8000000000000000U -#define uECC_WORD_BITS 64 -#define uECC_WORD_BITS_SHIFT 6 -#define uECC_WORD_BITS_MASK 0x03F +#define MG_UECC_WORD_BITS 64 +#define MG_UECC_WORD_BITS_SHIFT 6 +#define MG_UECC_WORD_BITS_MASK 0x03F -#endif /* uECC_WORD_SIZE */ +#endif /* MG_UECC_WORD_SIZE */ #endif /* _UECC_TYPES_H_ */ // End of uecc BSD-2 @@ -2292,7 +2140,6 @@ int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); -bool mg_http_match_uri(const struct mg_http_message *, const char *glob); long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, const char *dir, size_t max_size); void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); @@ -2321,10 +2168,11 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root, struct mg_tls_opts { - struct mg_str ca; // PEM or DER - struct mg_str cert; // PEM or DER - struct mg_str key; // PEM or DER - struct mg_str name; // If not empty, enable host name verification + struct mg_str ca; // PEM or DER + struct mg_str cert; // PEM or DER + struct mg_str key; // PEM or DER + struct mg_str name; // If not empty, enable host name verification + int skip_verification; // Skip certificate and host name verification }; void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts); @@ -2497,6 +2345,7 @@ struct mg_mqtt_opts { uint8_t qos; // message quality of service uint8_t version; // Can be 4 (3.1.1), or 5. If 0, assume 4 uint16_t keepalive; // Keep-alive timer in seconds + uint16_t retransmit_id; // For PUBLISH, init to 0 bool retain; // Retain flag bool clean; // Clean session flag struct mg_mqtt_prop *props; // MQTT5 props array @@ -2523,7 +2372,7 @@ struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data); void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts); -void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts); +uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts); void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts); int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *); void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, @@ -2770,6 +2619,8 @@ extern struct mg_tcpip_driver mg_tcpip_driver_imxrt; extern struct mg_tcpip_driver mg_tcpip_driver_same54; extern struct mg_tcpip_driver mg_tcpip_driver_cmsis; extern struct mg_tcpip_driver mg_tcpip_driver_ra; +extern struct mg_tcpip_driver mg_tcpip_driver_xmc; +extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -2863,6 +2714,8 @@ struct mg_profitem { #endif +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT + struct mg_tcpip_driver_imxrt_data { // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk), // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz @@ -2880,6 +2733,56 @@ struct mg_tcpip_driver_imxrt_data { uint8_t phy_addr; // PHY address }; +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 2 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 24 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_imxrt_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_imxrt; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: imxrt, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + + + +struct mg_phy { + uint16_t (*read_reg)(uint8_t addr, uint8_t reg); + void (*write_reg)(uint8_t addr, uint8_t reg, uint16_t value); +}; + +// PHY configuration settings, bitmask +enum { + MG_PHY_LEDS_ACTIVE_HIGH = + (1 << 0), // Set if PHY LEDs are connected to ground + MG_PHY_CLOCKS_MAC = + (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY +}; + +enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M }; + +void mg_phy_init(struct mg_phy *, uint8_t addr, uint8_t config); +bool mg_phy_up(struct mg_phy *, uint8_t addr, bool *full_duplex, + uint8_t *speed); + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA struct mg_tcpip_driver_ra_data { // MDC clock "divider". MDC clock is software generated, @@ -2888,11 +2791,24 @@ struct mg_tcpip_driver_ra_data { uint8_t phy_addr; // PHY address }; +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54 struct mg_tcpip_driver_same54_data { int mdc_cr; }; +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 5 +#endif + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ + MG_ENABLE_DRIVER_STM32F struct mg_tcpip_driver_stm32f_data { // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz @@ -2911,6 +2827,35 @@ struct mg_tcpip_driver_stm32f_data { uint8_t phy_addr; // PHY address }; +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_stm32f_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_stm32f; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \ + MG_ENABLE_DRIVER_STM32H struct mg_tcpip_driver_stm32h_data { // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz @@ -2921,12 +2866,43 @@ struct mg_tcpip_driver_stm32h_data { // 100-150 MHz HCLK/62 1 // 20-35 MHz HCLK/16 2 // 35-60 MHz HCLK/26 3 - // 150-250 MHz HCLK/102 4 <-- value for Nucleo-H* on max speed driven by HSI - // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on max speed driven by CSI + // 150-250 MHz HCLK/102 4 <-- value for max speed HSI + // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI // 110, 111 Reserved int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + + uint8_t phy_addr; // PHY address + uint8_t phy_conf; // PHY config }; +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_stm32h_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_stm32h; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C struct mg_tcpip_driver_tm4c_data { // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz @@ -2941,6 +2917,94 @@ struct mg_tcpip_driver_tm4c_data { int mdc_cr; // Valid values: -1, 0, 1, 2, 3 }; +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC + +struct mg_tcpip_driver_xmc_data { + // 13.2.8.1 Station Management Functions + // MDC clock divider (). MDC clock is derived from ETH MAC clock + // It must not exceed 2.5MHz + // ETH Clock range DIVIDER mdc_cr VALUE + // -------------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz ETH Clock/42 0 + // 100-150 MHz ETH Clock/62 1 + // 20-35 MHz ETH Clock/16 2 + // 35-60 MHz ETH Clock/26 3 + // 150-250 MHz ETH Clock/102 4 + // 250-300 MHz ETH Clock/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 4 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_xmc_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_xmc; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: xmc, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 + +struct mg_tcpip_driver_xmc7_data { + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_xmc7_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_xmc7; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + #ifdef __cplusplus } #endif diff --git a/dist/myGPIOd/CMakeLists.txt b/dist/myGPIOd/CMakeLists.txt new file mode 100644 index 000000000..fc584f095 --- /dev/null +++ b/dist/myGPIOd/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# SPDX-License-Identifier: GPL-3.0-or-later +# myGPIOd (c) 2020-2024 Juergen Mang +# https://github.com/jcorporation/myGPIOd +# + +add_library(mygpio "") + +configure_file(mygpio-common/compile_time.h.in "${PROJECT_BINARY_DIR}/dist/myGPIOd/compile_time.h") + +target_include_directories(mygpio PRIVATE + "${PROJECT_SOURCE_DIR}/dist/myGPIOd" + "${PROJECT_BINARY_DIR}/dist/myGPIOd" +) + +target_sources(mygpio + PRIVATE + libmygpio/src/buffer.c + libmygpio/src/connection.c + libmygpio/src/gpio_struct.c + libmygpio/src/gpio.c + libmygpio/src/gpioinfo.c + libmygpio/src/gpiolist.c + libmygpio/src/idle.c + libmygpio/src/parser.c + libmygpio/src/pair.c + libmygpio/src/protocol.c + libmygpio/src/socket.c + libmygpio/src/util.c + mygpio-common/util.c +) diff --git a/dist/myGPIOd/libmygpio/CMakeLists.txt b/dist/myGPIOd/libmygpio/CMakeLists.txt new file mode 100644 index 000000000..03a4ff8a3 --- /dev/null +++ b/dist/myGPIOd/libmygpio/CMakeLists.txt @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# myMPD (c) 2018-2023 Juergen Mang +# https://github.com/jcorporation/mympd + +add_library(mygpio SHARED) + +target_include_directories(mygpio + PRIVATE + ${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR} +) + +target_sources(mygpio + PRIVATE + src/buffer.c + src/connection.c + src/gpio_struct.c + src/gpio.c + src/gpioinfo.c + src/gpiolist.c + src/idle.c + src/parser.c + src/pair.c + src/protocol.c + src/socket.c + src/util.c +) + +set_target_properties(mygpio + PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR} +) + +target_link_libraries(mygpio + mygpio-common +) + +if (MYGPIOD_LIBRARY) + # Install shared library + install(TARGETS mygpio + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + + # example + add_subdirectory("example") +endif() + +if (MYGPIOD_HEADER) + # Install public headers + install(DIRECTORY include/libmygpio + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + + # pkg-config file + configure_file(libmygpio.pc.in libmygpio.pc @ONLY) + install(FILES ${CMAKE_BINARY_DIR}/libmygpio/libmygpio.pc + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig + ) +endif() diff --git a/dist/myGPIOd/libmygpio/example/CMakeLists.txt b/dist/myGPIOd/libmygpio/example/CMakeLists.txt new file mode 100644 index 000000000..0112505eb --- /dev/null +++ b/dist/myGPIOd/libmygpio/example/CMakeLists.txt @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# myMPD (c) 2018-2023 Juergen Mang +# https://github.com/jcorporation/mympd + +add_executable(mygpio-example "") + +target_include_directories(mygpio-example + PRIVATE + ${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR} +) + +target_sources(mygpio-example + PRIVATE + main.c +) + +target_link_directories(mygpio-example + PRIVATE + "${PROJECT_BINARY_DIR}/libmygpio" +) + +target_link_libraries(mygpio-example + PRIVATE + "mygpio" +) diff --git a/dist/myGPIOd/libmygpio/example/main.c b/dist/myGPIOd/libmygpio/example/main.c new file mode 100644 index 000000000..9c4d1156b --- /dev/null +++ b/dist/myGPIOd/libmygpio/example/main.c @@ -0,0 +1,148 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" +#include "libmygpio/include/libmygpio/libmygpio.h" + +#include +#include +#include + +int main(int argc, char **argv) { + (void) argc; + (void) argv; + + // Create a connection struct and connect to /run/mygpiod/socket + printf("Connecting to myGPIOd\n"); + struct t_mygpio_connection *conn = mygpio_connection_new(CFG_SOCKET_PATH, 5000); + if (conn == NULL) { + printf("Out of memory\n"); + return EXIT_FAILURE; + } + + // Check the connection state + if (mygpio_connection_get_state(conn) != MYGPIO_STATE_OK) { + printf("Error: %s\n", mygpio_connection_get_error(conn)); + mygpio_connection_free(conn); + return EXIT_FAILURE; + } + + // Retrieve the server version + const unsigned *version = mygpio_connection_get_version(conn); + printf("Connected, server version %u.%u.%u\n", version[0], version[1], version[2]); + + // Enter the myGPIOd idle mode to wait for events + // All timeouts are disabled + printf("Sending idle\n"); + if (mygpio_send_idle(conn) == true) { + printf("In idle mode\n"); + } + + // Wait 5 seconds for an idle event + printf("Waiting for idle events\n"); + if (mygpio_wait_idle(conn, 5000) == true) { + printf("Events occurred\n"); + struct t_mygpio_idle_event *event; + // Retrieve the list of events + while ((event = mygpio_recv_idle_event(conn)) != NULL) { + printf("GPIO %u, event %u, timestamp %llu ms\n", + mygpio_idle_event_get_gpio(event), + mygpio_idle_event_get_event(event), + (unsigned long long)mygpio_idle_event_get_timestamp_ms(event) + ); + mygpio_free_idle_event(event); + } + } + else { + printf("No events occurred\n"); + } + mygpio_response_end(conn); + + // Exit the idle mode to send commands to myGPIOd + printf("Sending noidle\n"); + if (mygpio_send_noidle(conn) == true) { + printf("Exited idle mode\n"); + } + mygpio_response_end(conn); + + // Lists all configured GPIOs with some settings + printf("Sending gpiolist\n"); + if (mygpio_gpiolist(conn) == true) { + struct t_mygpio_gpio *gpio; + printf("Retrieving gpio config\n"); + while ((gpio = mygpio_recv_gpio_list(conn)) != NULL) { + printf("GPIO %u, direction %u, value %d\n", + mygpio_gpio_get_gpio(gpio), + mygpio_gpio_get_direction(gpio), + mygpio_gpio_get_value(gpio) + ); + mygpio_free_gpio(gpio); + } + } + else { + printf("Error: %s\n", mygpio_connection_get_error(conn)); + mygpio_connection_clear_error(conn); + } + mygpio_response_end(conn); + + // List details from gpio 5 + struct t_mygpio_gpio *gpio; + if (mygpio_gpioinfo(conn, 5) == true && + (gpio = mygpio_recv_gpio_info(conn)) != NULL) + { + enum mygpio_gpio_direction direction = mygpio_gpio_get_direction(gpio); + if (direction == MYGPIO_GPIO_DIRECTION_IN) { + printf("Active low: %d\n", mygpio_gpio_in_get_active_low(gpio)); + printf("Bias: %s\n", mygpio_gpio_lookup_bias(mygpio_gpio_in_get_bias(gpio))); + printf("Event request: %s\n", mygpio_gpio_lookup_event_request(mygpio_gpio_in_get_event_request(gpio))); + printf("Is debounced: %d\n", mygpio_gpio_in_get_is_debounced(gpio)); + printf("Debounce period: %d us\n", mygpio_gpio_in_get_debounce_period_us(gpio)); + printf("Event clock: %s\n", mygpio_gpio_lookup_event_clock(mygpio_gpio_in_get_event_clock(gpio))); + } + else if (direction == MYGPIO_GPIO_DIRECTION_OUT) { + printf("Drive: %s\n", mygpio_gpio_lookup_drive(mygpio_gpio_out_get_drive(gpio))); + } + mygpio_free_gpio(gpio); + } + else { + fprintf(stderr, "Error: %s\n", mygpio_connection_get_error(conn)); + } + mygpio_response_end(conn); + + // Get the value of GPIO number 5 + // It must be configured as input or output GPIO in the configuration of myGPIOd. + printf("Sending gpioget 5\n"); + enum mygpio_gpio_value value = mygpio_gpioget(conn, 5); + if (value == MYGPIO_GPIO_VALUE_UNKNOWN) { + printf("Error: %s\n", mygpio_connection_get_error(conn)); + mygpio_connection_clear_error(conn); + } + else { + printf("Value: %u\n", value); + } + mygpio_response_end(conn); + + // Set the value of GPIO number 6 to active + // It must be configured as an ouput GPIO in the configuration of myGPIOd. + printf("Sending gpioset 6 active\n"); + if (mygpio_gpioset(conn, 6, MYGPIO_GPIO_VALUE_ACTIVE) == false) { + printf("Error: %s\n", mygpio_connection_get_error(conn)); + mygpio_connection_clear_error(conn); + } + + // Toggle the value of GPIO number 6 + printf("Sending gpiotoggle 6\n"); + if (mygpio_gpiotoggle(conn, 6) == false) { + printf("Error: %s\n", mygpio_connection_get_error(conn)); + mygpio_connection_clear_error(conn); + } + + // Close the connection + printf("Closing connection\n"); + mygpio_connection_free(conn); + + return EXIT_SUCCESS; +} diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio.h new file mode 100644 index 000000000..e3877727c --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio.h @@ -0,0 +1,33 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/** + * @mainpage + * + * This is the official client library for myGPIOd + * (https://github.com/jcorporation/myGPIOd), written in C. + * + * All functions and structs are documented in the following include files. + * Include only the libymgpio.h header in your project. + * + * You can find examples for usage in the example directory. + * + * \author Juergen Mang (mail@jcgames.de) + */ + +#ifndef LIBMYGPIO_H +#define LIBMYGPIO_H + +#include "libmygpio_connection.h" +#include "libmygpio_gpio_struct.h" +#include "libmygpio_gpio.h" +#include "libmygpio_gpioinfo.h" +#include "libmygpio_gpiolist.h" +#include "libmygpio_idle.h" +#include "libmygpio_parser.h" +#include "libmygpio_protocol.h" + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_connection.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_connection.h new file mode 100644 index 000000000..e91e2cbf8 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_connection.h @@ -0,0 +1,110 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_CONNECTION_H +#define LIBMYGPIO_CONNECTION_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @struct t_mygpio_connection + * @{ + * The opaque myGPIOd connection object. You can not access it directly. + * Refer to @ref libmygpio_connection for function that operate on this struct. + * @} + */ +struct t_mygpio_connection; + +/** + * @defgroup libmygpio_connection myGPIOd connection + * + * @brief This module provides functions for myGPIOd connection management. + * + * @{ + */ + +/** + * myGPIOd connections states + */ +enum mygpio_conn_state { + MYGPIO_STATE_OK, //!< OK state + MYGPIO_STATE_ERROR, //!< Error state, read the error with mygpio_connection_get_error and clear it with mygpio_connection_clear_error + MYGPIO_STATE_FATAL //!< Fatal state, read the error with mygpio_connection_get_error. You must reconnect to recover. +}; + +/** + * Creates a new connection to the myGPIOd socket and tries to connect. + * Check the state with mygpio_connection_get_state. + * It must be freed by the caller with mygpio_connection_free. + * @param socket_path Server socket to connect to. + * @param timeout_ms The read timeout in milliseconds + * @return Returns the t_mygpio_connection struct on NULL in a out of memory condition. + */ +struct t_mygpio_connection *mygpio_connection_new(const char *socket_path, int timeout_ms); + +/** + * Closes the connection and frees the t_mygpio_connection struct + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + */ +void mygpio_connection_free(struct t_mygpio_connection *connection); + +/** + * Gets the server version. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Unsigned array consisting of major, minor and patch version. + */ +const unsigned *mygpio_connection_get_version(struct t_mygpio_connection *connection); + +/** + * Returns the file descriptor of the underlying socket. + * You can use it to poll the file descriptor in an external event loop. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return File descriptor + */ +int mygpio_connection_get_fd(struct t_mygpio_connection *connection); + +/** + * Gets the current connection state. + * Use mygpio_connection_get_error to get the error message and mygpio_connection_clear_error to clear it. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return The connection state + */ +enum mygpio_conn_state mygpio_connection_get_state(struct t_mygpio_connection *connection); + +/** + * Gets the current error message. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Error message or NULL if no error is present + */ +const char *mygpio_connection_get_error(struct t_mygpio_connection *connection); + +/** + * Clears the current error message. + * MYGPIO_STATE_FATAL messages can not be cleared. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return true on success, else false + */ +bool mygpio_connection_clear_error(struct t_mygpio_connection *connection); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio.h new file mode 100644 index 000000000..0345318ad --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio.h @@ -0,0 +1,77 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_GPIO_H +#define LIBMYGPIO_GPIO_H + +#include "libmygpio_gpio_struct.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; + +/** + * @defgroup libmygpio_gpio_functions GPIO functions + * + * @brief This module provides functions to set and get values of a GPIO. + * + * @{ + */ + +/** + * Returns the current value of a configured input or output GPIO. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @return Value of the GPIO or MYGPIO_GPIO_VALUE_UNKNOWN on error. + */ +enum mygpio_gpio_value mygpio_gpioget(struct t_mygpio_connection *connection, unsigned gpio); + +/** + * Sets the value of a configured output GPIO. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @param value Value to set: MYGPIO_GPIO_VALUE_INACTIVE or MYGPIO_GPIO_VALUE_ACTIVE + * @return true on success, else false. + */ +bool mygpio_gpioset(struct t_mygpio_connection *connection, unsigned gpio, enum mygpio_gpio_value value); + +/** + * Toggles the value of a configured output GPIO. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @return true on success, else false. + */ +bool mygpio_gpiotoggle(struct t_mygpio_connection *connection, unsigned gpio); + +/** + * Toggles the value of a configured output GPIO at given timeout and interval. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @param timeout_ms timeout in milliseconds + * @param interval_ms interval in milliseconds, set it 0 to blink only once. + * @return true on success, else false. + */ +bool mygpio_gpioblink(struct t_mygpio_connection *connection, unsigned gpio, int timeout_ms, int interval_ms); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio_struct.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio_struct.h new file mode 100644 index 000000000..56e3dee04 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpio_struct.h @@ -0,0 +1,185 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_GPIO_STRUCT_H +#define LIBMYGPIO_GPIO_STRUCT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; + +/** + * @struct t_mygpio_gpio + * @{ + * The opaque GPIO object. You can not access it directly. + * Refer to @ref libmygpio_gpio_settings for function that operate on this struct. + * @} + */ +struct t_mygpio_gpio; + +/** + * @defgroup libmygpio_gpio_settings GPIO + * + * @brief This module provides functions to access the t_mygpio_gpio struct, + * received by gpioinfo or gpiolist. + * + * @{ + */ + +/** + * The direction of a GPIO. + */ +enum mygpio_gpio_direction { + MYGPIO_GPIO_DIRECTION_UNKNOWN = -1, //!< Unknown GPIO direction. + MYGPIO_GPIO_DIRECTION_IN, //!< Input direction, myGPIOd can read events from this GPIO. + MYGPIO_GPIO_DIRECTION_OUT //!< Output direction, myGPIOd can set the value to: MYGPIO_GPIO_VALUE_ACTIVE or MYGPIO_GPIO_VALUE_INACTIVE. +}; + +/** + * The value of a GPIO. + */ +enum mygpio_gpio_value { + MYGPIO_GPIO_VALUE_UNKNOWN = -1, //!< Unknown GPIO value + MYGPIO_GPIO_VALUE_INACTIVE, //!< GPIO state is inactive + MYGPIO_GPIO_VALUE_ACTIVE //!< GPIO state is active +}; + +/** + * Bias setting for an input GPIO. + */ +enum mygpio_gpio_bias { + MYGPIO_BIAS_UNKNOWN = -1, //!< Unknown bias setting + MYGPIO_BIAS_AS_IS, //!< Do not touch the bias state + MYGPIO_BIAS_DISABLED, //!< Disable the bias + MYGPIO_BIAS_PULL_DOWN, //!< Pull-down the GPIO + MYGPIO_BIAS_PULL_UP //!< Pull-up the GPIO +}; + +/** + * Events requested for an input GPIO. + */ +enum mygpio_event_request { + MYGPIO_EVENT_REQUEST_UNKNOWN = -1, //!< Unknown event request setting + MYGPIO_EVENT_REQUEST_FALLING, //!< Request falling events + MYGPIO_EVENT_REQUEST_RISING, //!< Request rising events + MYGPIO_EVENT_REQUEST_BOTH //!< Request falling and rising events +}; + +/** + * Clock setting for an input GPIO. + */ +enum mygpio_event_clock { + MYGPIO_EVENT_CLOCK_UNKNOWN = -1, //!< Unknown event clock setting + MYGPIO_EVENT_CLOCK_MONOTONIC, //!< Monotonic clock + MYGPIO_EVENT_CLOCK_REALTIME, //!< Realtime clock + MYGPIO_EVENT_CLOCK_HTE //!< Hardware timestamp engine +}; + +/** + * Drive setting for an output GPIO. + */ +enum mygpio_drive { + MYGPIO_DRIVE_UNKNOWN = -1, //!< Unknown drive setting + MYGPIO_DRIVE_PUSH_PULL, //!< Drive setting is push-pull + MYGPIO_DRIVE_OPEN_DRAIN, //!< Drive setting is open-drain + MYGPIO_DRIVE_OPEN_SOURCE //!< Drive setting is open-source +}; + +/** + * Returns the GPIO number from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO number. + */ +unsigned mygpio_gpio_get_gpio(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO direction from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO direction, one of enum mygpio_gpio_direction. + */ +enum mygpio_gpio_direction mygpio_gpio_get_direction(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO value from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO value, one of enum mygpio_gpio_value. + */ +enum mygpio_gpio_value mygpio_gpio_get_value(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO active_low from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO is set to active_low? + */ +bool mygpio_gpio_in_get_active_low(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO bias from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO bias, one of enum mygpio_gpio_bias. + */ +enum mygpio_gpio_bias mygpio_gpio_in_get_bias(struct t_mygpio_gpio *gpio); + +/** + * Returns the requested events from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return requested GPIO events, one of enum event_request. + */ +enum mygpio_event_request mygpio_gpio_in_get_event_request(struct t_mygpio_gpio *gpio); + +/** + * Returns true if the GPIO is debounced. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO debounced? + */ +bool mygpio_gpio_in_get_is_debounced(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO debounce period from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO debounce period in microseconds. + */ +int mygpio_gpio_in_get_debounce_period_us(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO event clock from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO event clock, one of enum mygpio_event_clock. + */ +enum mygpio_event_clock mygpio_gpio_in_get_event_clock(struct t_mygpio_gpio *gpio); + +/** + * Returns the GPIO drive setting from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO drive setting, one of enum mygpio_drive. + */ +enum mygpio_drive mygpio_gpio_out_get_drive(struct t_mygpio_gpio *gpio); + +/** + * Frees the struct received by mygpio_recv_gpio. + * @param gpio Pointer to struct mygpio_recv_gpio. + */ +void mygpio_free_gpio(struct t_mygpio_gpio *gpio); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpioinfo.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpioinfo.h new file mode 100644 index 000000000..fa658d876 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpioinfo.h @@ -0,0 +1,61 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_GPIOINFO_H +#define LIBMYGPIO_GPIOINFO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; +struct t_mygpio_gpio; + +/** + * @defgroup libmygpio_gpioinfo GPIO info + * + * @brief This module provides functions for the gpioinfo protocol command. + * + * @{ + */ + +/** + * Lists the current settings of a GPIO. + * Retrieve the settings with mygpio_recv_gpio_info and end the response with mygpio_response_end. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @return bool true on success, else false. + */ +bool mygpio_gpioinfo(struct t_mygpio_connection *connection, unsigned gpio); + +/** + * Receives the result of mygpio_gpioinfo. + * Free it with mygpio_free_gpio. + * Use the mygpio_gpio_get_gpio_*, mygpio_gpio_in_get_gpio_* and mygpio_gpio_in_get_gpio_* + * functions to access the values. + * The caller must free it with mygpio_free_gpio. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Allocated struct t_mygpio_gpio or NULL on list end or error. + */ +struct t_mygpio_gpio *mygpio_recv_gpio_info(struct t_mygpio_connection *connection); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpiolist.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpiolist.h new file mode 100644 index 000000000..f2b3d33f2 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_gpiolist.h @@ -0,0 +1,58 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_GPIOLIST_H +#define LIBMYGPIO_GPIOLIST_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; +struct t_mygpio_gpio; + +/** + * @defgroup libmygpio_gpiolist GPIO list + * + * @brief This module provides functions for the gpiolist protocol command. + * + * @{ + */ + +/** + * Lists the modes and values of all configured GPIOs. + * Retrieve the list elements with mygpio_recv_gpio_list and end the response with mygpio_response_end. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return bool true on success, else false. + */ +bool mygpio_gpiolist(struct t_mygpio_connection *connection); + +/** + * Receives a list element of mygpio_gpiolist. + * Use the mygpio_gpio_get_gpio_* functions to access the values. + * The caller must free it with mygpio_free_gpio. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Allocated struct t_mygpio_gpio or NULL on list end or error. + */ +struct t_mygpio_gpio *mygpio_recv_gpio_list(struct t_mygpio_connection *connection); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_idle.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_idle.h new file mode 100644 index 000000000..f3c4fa505 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_idle.h @@ -0,0 +1,144 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_IDLE_H +#define LIBMYGPIO_IDLE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; + +/** + * @struct t_mygpio_idle_event + * @{ + * The opaque myGPIOd idle event object. You can not access it directly. + * Refer to @ref libmygpio_idle_event for function that operate on this struct. + * @} + */ +struct t_mygpio_idle_event; + +/** + * @defgroup libmygpio_idle_event Idle events + * + * @brief This module provides functions for the idle mode. + * + * @{ + */ + +/** + * Possible event types + */ +enum mygpio_event { + MYGPIO_EVENT_UNKNOWN = -1, //!< unknown + MYGPIO_EVENT_FALLING, //!< falling + MYGPIO_EVENT_RISING, //!< rising + MYGPIO_EVENT_LONG_PRESS, //!< long_press + MYGPIO_EVENT_LONG_PRESS_RELEASE //!< long_press release +}; + +/** + * Parses a string to the event type. + * @param str String to parse + * @return enum mygpio_event + */ +enum mygpio_event mygpio_parse_event(const char *str); + +/** + * Lookups the name for the event. + * @param event event type + * @return Event name as string + */ +const char *mygpio_lookup_event(enum mygpio_event event); + +/** + * Enters the myGPIOd idle mode to get notifications about events. + * Retrieve the list of events with mygpio_recv_idle_event. + * In this mode no commands but mygpio_send_noidle are allowed. + * All timeouts are disabled. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return true on success, else false + */ +bool mygpio_send_idle(struct t_mygpio_connection *connection); + +/** + * Exits the myGPIOd idle mode. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return true on success, else false + */ +bool mygpio_send_noidle(struct t_mygpio_connection *connection); + +/** + * Waits until an event occurs or the timeout expires. + * It returns instantly if events had occurred while not in idle mode. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param timeout Timeout in milliseconds, -1 for no timeout + * @return true if an event has occurred, false on timeout or error. + */ +bool mygpio_wait_idle(struct t_mygpio_connection *connection, int timeout); + +/** + * Receives a list element of the waiting idle events. + * Access the values with the mygpio_idle_event_get_* functions. + * The caller must free it with mygpio_free_idle_event. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Allocated struct t_mygpio_idle_event or NULL on list end or error. + */ +struct t_mygpio_idle_event *mygpio_recv_idle_event(struct t_mygpio_connection *connection); + +/** + * Returns the GPIO number from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return GPIO number. + */ +unsigned mygpio_idle_event_get_gpio(struct t_mygpio_idle_event *event); + +/** + * Returns the event type from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The event type, one of enum mygpio_event. + */ +enum mygpio_event mygpio_idle_event_get_event(struct t_mygpio_idle_event *event); + +/** + * Returns the event type name from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The event type name + */ +const char *mygpio_idle_event_get_event_name(struct t_mygpio_idle_event *event); + +/** + * Returns the timestamp from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The timestamp in milliseconds. + */ +uint64_t mygpio_idle_event_get_timestamp_ms(struct t_mygpio_idle_event *event); + +/** + * Frees the struct received by mygpio_recv_idle_event + * @param event Pointer to struct t_mygpio_idle_event. + */ +void mygpio_free_idle_event(struct t_mygpio_idle_event *event); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_parser.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_parser.h new file mode 100644 index 000000000..4fde5f7d4 --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_parser.h @@ -0,0 +1,124 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_PARSER_H +#define LIBMYGPIO_PARSER_H + +#include "libmygpio_gpio_struct.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libmygpio_parser Parser + * + * @brief This module provides parsing and lookup functions for GPIO settings. + * + * @{ + */ + +/** + * Lookups the name for the gpio direction. + * @param direction the gpio direction. + * @return gpio direction name + */ +const char *mygpio_gpio_lookup_direction(enum mygpio_gpio_direction direction); + +/** + * Parses a string to the gpio direction. + * @param str string to parse + * @return direction of the gpio + */ +enum mygpio_gpio_direction mygpio_gpio_parse_direction(const char *str); + +/** + * Lookups the name for the gpio value. + * @param value the gpio value. + * @return gpio value name + */ +const char *mygpio_gpio_lookup_value(enum mygpio_gpio_value value); + +/** + * Parses a string to a gpio value. + * @param str string to parse + * @return gpio value or GPIO_VALUE_UNKNOWN on error + */ +enum mygpio_gpio_value mygpio_gpio_parse_value(const char *str); + +/** + * Lookups the name for the gpio bias. + * @param bias the gpio bias. + * @return gpio bias name + */ +const char *mygpio_gpio_lookup_bias(enum mygpio_gpio_bias bias); + +/** + * Parses a string to a gpio bias. + * @param str string to parse + * @return gpio bias or GPIO_BIAS_UNKNOWN on error + */ +enum mygpio_gpio_bias mygpio_gpio_parse_bias(const char *str); + +/** + * Lookups the name for an event request. + * @param event_request the gpio event request. + * @return gpio event request name + */ +const char *mygpio_gpio_lookup_event_request(enum mygpio_event_request event_request); + +/** + * Parses a string to an event request. + * @param str string to parse + * @return gpio event request or GPIO_EVENT_REQUEST_UNKNOWN on error + */ +enum mygpio_event_request mygpio_gpio_parse_event_request(const char *str); + +/** + * Lookups the name for the gpio event clock. + * @param clock the gpio clock. + * @return gpio clock name + */ +const char *mygpio_gpio_lookup_event_clock(enum mygpio_event_clock clock); + +/** + * Parses a string to a gpio event clock. + * @param str string to parse + * @return gpio event clock or MYGPIO_EVENT_CLOCK_UNKNOWN on error + */ +enum mygpio_event_clock mygpio_gpio_parse_event_clock(const char *str); + +/** + * Lookups the name for the gpio drive setting. + * @param drive the gpio drive. + * @return gpio drive name + */ +const char *mygpio_gpio_lookup_drive(enum mygpio_drive drive); + +/** + * Parses a string to a gpio drive. + * @param str string to parse + * @return gpio bias or MYGPIO_DRIVE_UNKNOWN on error + */ +enum mygpio_drive mygpio_gpio_parse_drive(const char *str); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_protocol.h b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_protocol.h new file mode 100644 index 000000000..f63657aae --- /dev/null +++ b/dist/myGPIOd/libmygpio/include/libmygpio/libmygpio_protocol.h @@ -0,0 +1,47 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +/*! \file + * \brief myGPIOd client library + * + * Do not include this header directly. Use libmygpio/libmygpio.h instead. + */ + +#ifndef LIBMYGPIO_PROTOCOL_H +#define LIBMYGPIO_PROTOCOL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct t_mygpio_connection; + +/** + * @defgroup libmygpio_protocol Protocol + * + * @brief This module provides generic myGPIOd protocol functions. + * + * @{ + */ + +/** + * Finishes reading the response from myGPIOd and empties the input buffer. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return true on success, else false + */ +bool mygpio_response_end(struct t_mygpio_connection *connection); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dist/myGPIOd/libmygpio/libmygpio.pc.in b/dist/myGPIOd/libmygpio/libmygpio.pc.in new file mode 100644 index 000000000..b2bfea404 --- /dev/null +++ b/dist/myGPIOd/libmygpio/libmygpio.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/libmygpio + +Name: libmygpio +Description: Client library for myGPIOd +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -llibmygpio +Cflags: -I${includedir} diff --git a/dist/myGPIOd/libmygpio/src/buffer.c b/dist/myGPIOd/libmygpio/src/buffer.c new file mode 100644 index 000000000..f18ae72a2 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/buffer.c @@ -0,0 +1,19 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/buffer.h" + +#include +#include + +/** + * Initializes the buffer struct + * @param buf + */ +void libmygpio_buf_init(struct t_buf *buf) { + buf->buffer[0] = '\0'; + buf->len = 0; +} diff --git a/dist/myGPIOd/libmygpio/src/buffer.h b/dist/myGPIOd/libmygpio/src/buffer.h new file mode 100644 index 000000000..0e061c088 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/buffer.h @@ -0,0 +1,24 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_BUFFER_H +#define LIBMYGPIO_SRC_BUFFER_H + +#include + +#define BUFFER_SIZE_MAX 1025 + +/** + * Struct for input and output buffers + */ +struct t_buf { + char buffer[BUFFER_SIZE_MAX]; //!< the buffer + size_t len; //!< current size +}; + +void libmygpio_buf_init(struct t_buf *buf); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/connection.c b/dist/myGPIOd/libmygpio/src/connection.c new file mode 100644 index 000000000..5a429fdff --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/connection.c @@ -0,0 +1,142 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/connection.h" + +#include "libmygpio/include/libmygpio/libmygpio_protocol.h" +#include "libmygpio/src/protocol.h" +#include "libmygpio/src/socket.h" +#include "libmygpio/src/util.h" + +#include +#include +#include + +/** + * Creates a new connection and checks the initial server response + * @param socket_path unix socket path to connect + * @param timeout connection timeout in ms + * @return allocated connection struct or NULL if malloc fails + */ +struct t_mygpio_connection *mygpio_connection_new(const char *socket_path, int timeout_ms) { + struct t_mygpio_connection *connection = malloc(sizeof(struct t_mygpio_connection)); + if (connection == NULL) { + return NULL; + } + libmygpio_buf_init(&connection->buf_in); + libmygpio_buf_init(&connection->buf_out); + connection->version[0] = 0; + connection->version[1] = 0; + connection->version[2] = 0; + connection->error = NULL; + connection->timeout_ms = timeout_ms; + connection->state = MYGPIO_STATE_OK; + connection->fd = libmygpio_socket_connect(socket_path); + if (connection->fd == -1) { + libmygpio_connection_set_state(connection, MYGPIO_STATE_FATAL, "Connection failed"); + return connection; + } + if (libmygpio_recv_response_status(connection) == false || + libmygpio_recv_version(connection) == false || + mygpio_response_end(connection) == false) + { + libmygpio_connection_set_state(connection, MYGPIO_STATE_FATAL, "Handshake failed"); + return connection; + } + return connection; +} + +/** + * Closes and frees the connection + * @param connection connection struct + */ +void mygpio_connection_free(struct t_mygpio_connection *connection) { + if (connection != NULL) { + libmygpio_socket_close(connection->fd); + free(connection); + } +} + +/** + * Checks the connection state + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_connection_check(struct t_mygpio_connection *connection) { + return connection->state == MYGPIO_STATE_OK && + connection->fd > -1; +} + +/** + * Sets connection state and error message + * @param connection connection struct + * @param state state enum + * @param message error message + */ +void libmygpio_connection_set_state(struct t_mygpio_connection *connection, + enum mygpio_conn_state state, const char *message) +{ + connection->state = state; + if (connection->error != NULL) { + free(connection->error); + } + if (message != NULL) { + LIBMYGPIO_LOG("Server error: %s", message); + connection->error = strdup(message); + } + else { + connection->error = NULL; + } +} + +/** + * Gets the connection state + * @param connection connection struct + * @return the connection state + */ +enum mygpio_conn_state mygpio_connection_get_state(struct t_mygpio_connection *connection) { + return connection->state; +} + +/** + * Gets the connection error message + * @param connection connection struct + * @return the current error message + */ +const char *mygpio_connection_get_error(struct t_mygpio_connection *connection) { + return connection->error; +} + +/** + * Clears the error state. + * @param connection connection struct + * @return true on success, for fatal errors false + */ +bool mygpio_connection_clear_error(struct t_mygpio_connection *connection) { + if (connection->state == MYGPIO_STATE_FATAL) { + return false; + } + libmygpio_connection_set_state(connection, MYGPIO_STATE_OK, NULL); + return true; +} + +/** + * Returns the connection version string + * @param connection connection struct + * @return the myGPIOd version + */ +const unsigned *mygpio_connection_get_version(struct t_mygpio_connection *connection) { + return connection->version; +} + +/** + * Returns the connection file descriptor + * @param connection connection struct + * @return file descriptor or -1 if not connected + */ +int mygpio_connection_get_fd(struct t_mygpio_connection *connection) { + return connection->fd; +} diff --git a/dist/myGPIOd/libmygpio/src/connection.h b/dist/myGPIOd/libmygpio/src/connection.h new file mode 100644 index 000000000..2c2de3e9c --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/connection.h @@ -0,0 +1,28 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_CONNECTION_H +#define LIBMYGPIO_SRC_CONNECTION_H + +#include "libmygpio/include/libmygpio/libmygpio_connection.h" +#include "libmygpio/src/buffer.h" + +struct t_mygpio_connection { + int fd; //!< myGPIOd socket + char *socket_path; //!< path to myGPIOd socket + struct t_buf buf_in; //!< input buffer + struct t_buf buf_out; //!< output buffer + unsigned version[3]; //!< myGPIOd version + int timeout_ms; //!< connection timeout in ms + enum mygpio_conn_state state; //!< connection state + char *error; //!< error message +}; + +void libmygpio_connection_set_state(struct t_mygpio_connection *connection, + enum mygpio_conn_state state, const char *message); +bool mygpio_connection_check(struct t_mygpio_connection *connection); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/gpio.c b/dist/myGPIOd/libmygpio/src/gpio.c new file mode 100644 index 000000000..036301e42 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/gpio.c @@ -0,0 +1,79 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" +#include "libmygpio/include/libmygpio/libmygpio_gpio.h" +#include "libmygpio/include/libmygpio/libmygpio_parser.h" +#include "libmygpio/include/libmygpio/libmygpio_protocol.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/protocol.h" + +#include +#include +#include +#include + +/** + * Receives the value of an input or output gpio + * @param connection connection struct + * @param gpio gpio number (0-64) + * @return value of the gpio or MYGPIO_GPIO_VALUE_UNKNOWN on error + */ +enum mygpio_gpio_value mygpio_gpioget(struct t_mygpio_connection *connection, unsigned gpio) { + enum mygpio_gpio_value value; + struct t_mygpio_pair *pair; + if (gpio > GPIOS_MAX) { + return MYGPIO_GPIO_VALUE_UNKNOWN; + } + if (libmygpio_send_line(connection, "gpioget %u", gpio) == false || + libmygpio_recv_response_status(connection) == false || + (pair = mygpio_recv_pair(connection)) == NULL || + strcmp(pair->name, "value") != 0 || + (value = mygpio_gpio_parse_value(pair->value)) == MYGPIO_GPIO_VALUE_UNKNOWN) + { + return MYGPIO_GPIO_VALUE_UNKNOWN; + } + return value; +} + +/** + * Sets the value of a configured output gpio + * @param connection connection struct + * @param gpio gpio number (0-64) + * @param value gpio value + * @return true on success, else false + */ +bool mygpio_gpioset(struct t_mygpio_connection *connection, unsigned gpio, enum mygpio_gpio_value value) { + return libmygpio_send_line(connection, "gpioset %u %s", gpio, mygpio_gpio_lookup_value(value)) && + libmygpio_recv_response_status(connection) && + mygpio_response_end(connection); +} + +/** + * Toggles the value of a configured output GPIO. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @return true on success, else false. + */ +bool mygpio_gpiotoggle(struct t_mygpio_connection *connection, unsigned gpio) { + return libmygpio_send_line(connection, "gpiotoggle %u", gpio) && + libmygpio_recv_response_status(connection) && + mygpio_response_end(connection); +} + +/** + * Toggles the value of a configured output GPIO at given timeout and interval. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @param gpio GPIO number + * @param timeout_ms timeout in milliseconds + * @param interval_ms interval in milliseconds, set it 0 to blink only once. + * @return true on success, else false. + */ +bool mygpio_gpioblink(struct t_mygpio_connection *connection, unsigned gpio, int timeout_ms, int interval_ms) { + return libmygpio_send_line(connection, "gpioblink %u %d %d", gpio, timeout_ms, interval_ms) && + libmygpio_recv_response_status(connection) && + mygpio_response_end(connection); +} diff --git a/dist/myGPIOd/libmygpio/src/gpio_struct.c b/dist/myGPIOd/libmygpio/src/gpio_struct.c new file mode 100644 index 000000000..8a28f0ee5 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/gpio_struct.c @@ -0,0 +1,144 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" +#include "libmygpio/src/gpio_struct.h" + +#include +#include +#include +#include + +/** + * Returns the GPIO number from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO number. + */ +unsigned mygpio_gpio_get_gpio(struct t_mygpio_gpio *gpio) { + return gpio->gpio; +} + +/** + * Returns the GPIO direction from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO direction, one of enum mygpio_gpio_direction. + */ +enum mygpio_gpio_direction mygpio_gpio_get_direction(struct t_mygpio_gpio *gpio) { + return gpio->direction; +} + +/** + * Returns the GPIO value from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO value, one of enum mygpio_gpio_value. + */ +enum mygpio_gpio_value mygpio_gpio_get_value(struct t_mygpio_gpio *gpio) { + return gpio->value; +} + +/** + * Returns the GPIO active_low from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO is set to active_low? + */ +bool mygpio_gpio_in_get_active_low(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->active_low; +} + +/** + * Returns the GPIO bias from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO bias, one of enum mygpio_gpio_bias. + */ +enum mygpio_gpio_bias mygpio_gpio_in_get_bias(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->bias; +} + +/** + * Returns the requested events from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return requested GPIO events, one of enum event_request. + */ +enum mygpio_event_request mygpio_gpio_in_get_event_request(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->event_request; +} + +/** + * Returns if the GPIO is debounced. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO debounced? + */ +bool mygpio_gpio_in_get_is_debounced(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->is_debounced; +} + +/** + * Returns the GPIO debounce period from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO debounce period in microseconds. + */ +int mygpio_gpio_in_get_debounce_period_us(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->debounce_period_us; +} + +/** + * Returns the GPIO event clock from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO event clock, one of enum mygpio_event_clock. + */ +enum mygpio_event_clock mygpio_gpio_in_get_event_clock(struct t_mygpio_gpio *gpio) { + assert(gpio->in); + return gpio->in->event_clock; +} + +/** + * Returns the GPIO drive setting from struct t_mygpio_gpio. + * @param gpio Pointer to struct t_mygpio_gpio. + * @return GPIO drive setting, one of enum mygpio_drive. + */ +enum mygpio_drive mygpio_gpio_out_get_drive(struct t_mygpio_gpio *gpio) { + assert(gpio->out); + return gpio->out->drive; +} + +/** + * Creates a new gpio struct + * @return struct t_mygpio_gpio* + */ +struct t_mygpio_gpio *mygpio_gpio_new(enum mygpio_gpio_direction direction) { + struct t_mygpio_gpio *gpio = malloc(sizeof(struct t_mygpio_gpio)); + assert(gpio); + gpio->in = NULL; + gpio->out = NULL; + if (direction == MYGPIO_GPIO_DIRECTION_IN) { + gpio->in = malloc(sizeof(struct t_mygpio_in)); + assert(gpio->in); + } + else if (direction == MYGPIO_GPIO_DIRECTION_OUT) { + gpio->out = malloc(sizeof(struct t_mygpio_out)); + assert(gpio->out); + } + return gpio; +} + +/** + * Frees the gpio struct + * @param gpio + */ +void mygpio_free_gpio(struct t_mygpio_gpio *gpio) { + if (gpio->in != NULL) { + free(gpio->in); + } + if (gpio->out != NULL) { + free(gpio->out); + } + free(gpio); +} diff --git a/dist/myGPIOd/libmygpio/src/gpio_struct.h b/dist/myGPIOd/libmygpio/src/gpio_struct.h new file mode 100644 index 000000000..5574f19c1 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/gpio_struct.h @@ -0,0 +1,38 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_GPIO_H +#define LIBMYGPIO_SRC_GPIO_H + +#include "libmygpio/include/libmygpio/libmygpio_gpio_struct.h" + +struct t_mygpio_in { + bool active_low; + enum mygpio_gpio_bias bias; + enum mygpio_event_request event_request; + bool is_debounced; + int debounce_period_us; + enum mygpio_event_clock event_clock; +}; + +struct t_mygpio_out { + enum mygpio_drive drive; +}; + +/** + * Struct holding the configuration of a GPIO. + */ +struct t_mygpio_gpio { + unsigned gpio; //!< GPIO number + enum mygpio_gpio_direction direction; //!< GPIO direction + enum mygpio_gpio_value value; //!< GPIO value + struct t_mygpio_in *in; //!< GPIO settings for input + struct t_mygpio_out *out; //!< GPIO settings for output +}; + +struct t_mygpio_gpio *mygpio_gpio_new(enum mygpio_gpio_direction direction); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/gpioinfo.c b/dist/myGPIOd/libmygpio/src/gpioinfo.c new file mode 100644 index 000000000..1b21aa4fc --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/gpioinfo.c @@ -0,0 +1,155 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" + +#include "libmygpio/include/libmygpio/libmygpio_gpioinfo.h" +#include "libmygpio/include/libmygpio/libmygpio_parser.h" +#include "libmygpio/src/gpio_struct.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/protocol.h" +#include "mygpio-common/util.h" + +#include +#include +#include +#include +#include + +/** + * Send the gpioinfo command and receives the status + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_gpioinfo(struct t_mygpio_connection *connection, unsigned gpio) { + return libmygpio_send_line(connection, "gpioinfo %u", gpio) && + libmygpio_recv_response_status(connection); +} + +/** + * Receives a list element of mygpio_gpioinfo. + * Free it with mygpio_free_gpio. + * @param connection Pointer to the connection struct returned by mygpio_connection_new. + * @return Allocated struct t_mygpio_gpio or NULL on list end or error. + */ +struct t_mygpio_gpio *mygpio_recv_gpio_info(struct t_mygpio_connection *connection) { + unsigned gpio_nr; + enum mygpio_gpio_direction direction; + enum mygpio_gpio_value value; + bool active_low = false; + enum mygpio_gpio_bias bias = MYGPIO_BIAS_UNKNOWN; + enum mygpio_event_request event_request = MYGPIO_EVENT_REQUEST_UNKNOWN; + bool is_debounced = false; + int debounce_period_us = 0; + enum mygpio_event_clock event_clock = MYGPIO_EVENT_CLOCK_UNKNOWN; + enum mygpio_drive drive = MYGPIO_DRIVE_UNKNOWN; + + struct t_mygpio_pair *pair; + if ((pair = mygpio_recv_pair_name(connection, "gpio")) == NULL) { + return NULL; + } + if (mygpio_parse_uint(pair->value, &gpio_nr, NULL, 0, GPIOS_MAX) == false) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "direction")) == NULL) { + return NULL; + } + if ((direction = mygpio_gpio_parse_direction(pair->value)) == MYGPIO_GPIO_DIRECTION_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "value")) == NULL) { + return NULL; + } + if ((value = mygpio_gpio_parse_value(pair->value)) == MYGPIO_GPIO_VALUE_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if (direction == MYGPIO_GPIO_DIRECTION_IN) { + if ((pair = mygpio_recv_pair_name(connection, "active_low")) == NULL) { + return NULL; + } + active_low = mygpio_parse_bool(pair->value); + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "bias")) == NULL) { + return NULL; + } + if ((bias = mygpio_gpio_parse_bias(pair->value)) == MYGPIO_BIAS_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "event_request")) == NULL) { + return NULL; + } + if ((event_request = mygpio_gpio_parse_event_request(pair->value)) == MYGPIO_EVENT_REQUEST_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "is_debounced")) == NULL) { + return NULL; + } + is_debounced = mygpio_parse_bool(pair->value); + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "debounce_period_us")) == NULL) { + return NULL; + } + if (mygpio_parse_int(pair->value, &debounce_period_us, NULL, 0, INT_MAX) == false) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "event_clock")) == NULL) { + return NULL; + } + if ((event_clock = mygpio_gpio_parse_event_clock(pair->value)) == MYGPIO_EVENT_CLOCK_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + } + else if (direction == MYGPIO_GPIO_DIRECTION_OUT) { + if ((pair = mygpio_recv_pair_name(connection, "drive")) == NULL) { + return NULL; + } + if ((drive = mygpio_gpio_parse_drive(pair->value)) == MYGPIO_DRIVE_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + } + + struct t_mygpio_gpio *gpio = mygpio_gpio_new(direction); + assert(gpio); + gpio->gpio = gpio_nr; + gpio->direction = direction; + gpio->value = value; + if (direction == MYGPIO_GPIO_DIRECTION_IN) { + gpio->in->active_low = active_low; + gpio->in->bias = bias; + gpio->in->event_request = event_request; + gpio->in->is_debounced = is_debounced; + gpio->in->debounce_period_us = debounce_period_us; + gpio->in->event_clock = event_clock; + } + else if (direction == MYGPIO_GPIO_DIRECTION_OUT) { + gpio->out->drive = drive; + } + return gpio; +} diff --git a/dist/myGPIOd/libmygpio/src/gpiolist.c b/dist/myGPIOd/libmygpio/src/gpiolist.c new file mode 100644 index 000000000..41031fe57 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/gpiolist.c @@ -0,0 +1,74 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" + +#include "libmygpio/include/libmygpio/libmygpio_gpiolist.h" +#include "libmygpio/include/libmygpio/libmygpio_parser.h" +#include "libmygpio/src/gpio_struct.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/protocol.h" +#include "mygpio-common/util.h" + +#include +#include +#include +#include + +/** + * Send the gpiolist command and receives the status + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_gpiolist(struct t_mygpio_connection *connection) { + return libmygpio_send_line(connection, "gpiolist") && + libmygpio_recv_response_status(connection); +} + +/** + * Receives the response for the gpiolist command + * @param connection connection struct + * @return gpio config struct or NULL on error or list end + */ +struct t_mygpio_gpio *mygpio_recv_gpio_list(struct t_mygpio_connection *connection) { + unsigned gpio_nr; + enum mygpio_gpio_direction direction; + enum mygpio_gpio_value value; + + struct t_mygpio_pair *pair; + if ((pair = mygpio_recv_pair_name(connection, "gpio")) == NULL) { + return NULL; + } + if (mygpio_parse_uint(pair->value, &gpio_nr, NULL, 0, GPIOS_MAX) == false) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "direction")) == NULL) { + return NULL; + } + if ((direction = mygpio_gpio_parse_direction(pair->value)) == MYGPIO_GPIO_DIRECTION_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "value")) == NULL) { + return NULL; + } + if ((value = mygpio_gpio_parse_value(pair->value)) == MYGPIO_GPIO_VALUE_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + struct t_mygpio_gpio *gpio = mygpio_gpio_new(MYGPIO_GPIO_DIRECTION_UNKNOWN); + gpio->gpio = gpio_nr; + gpio->direction = direction; + gpio->value = value; + return gpio; +} diff --git a/dist/myGPIOd/libmygpio/src/idle.c b/dist/myGPIOd/libmygpio/src/idle.c new file mode 100644 index 000000000..28b6fc7f5 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/idle.c @@ -0,0 +1,183 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" + +#include "libmygpio/include/libmygpio/libmygpio_idle.h" +#include "libmygpio/src/idle.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/protocol.h" +#include "mygpio-common/util.h" + +#include +#include +#include +#include + +/** + * Parses a string to the event type. + * @param str String to parse + * @return enum mygpio_event + */ +enum mygpio_event mygpio_parse_event(const char *str) { + if (strcmp(str, "falling") == 0) { + return MYGPIO_EVENT_FALLING; + } + if (strcmp(str, "rising") == 0) { + return MYGPIO_EVENT_RISING; + } + if (strcmp(str, "long_press") == 0) { + return MYGPIO_EVENT_LONG_PRESS; + } + if (strcmp(str, "long_press_release") == 0) { + return MYGPIO_EVENT_LONG_PRESS_RELEASE; + } + return MYGPIO_EVENT_UNKNOWN; +} + +/** + * Lookups the name for the event. + * @param event event type + * @return Event name as string + */ +const char *mygpio_lookup_event(enum mygpio_event event) { + switch(event) { + case MYGPIO_EVENT_FALLING: + return "falling"; + case MYGPIO_EVENT_RISING: + return "rising"; + case MYGPIO_EVENT_LONG_PRESS: + return "long_press"; + case MYGPIO_EVENT_LONG_PRESS_RELEASE: + return "long_press_release"; + case MYGPIO_EVENT_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Sends the idle command + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_send_idle(struct t_mygpio_connection *connection) { + return libmygpio_send_line(connection, "idle"); +} + +/** + * Sends the noidle command + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_send_noidle(struct t_mygpio_connection *connection) { + return libmygpio_send_line(connection, "noidle") && + libmygpio_recv_response_status(connection); +} + +/** + * Waits for an idle event + * @param connection connection struct + * @param timeout timeout in ms, use -1 to wait without a timeout + * @return true if events are waiting, false on timeout or polling has failed + */ +bool mygpio_wait_idle(struct t_mygpio_connection *connection, int timeout) { + struct pollfd pfds[1]; + pfds[0].fd = mygpio_connection_get_fd(connection); + pfds[0].events = POLLIN; + int events = poll(pfds, 1, timeout); + return events > 0 ? true : false; +} + +/** + * Receives an idle event + * @param connection connection struct + * @return idle event or NULL on error or list end + */ +struct t_mygpio_idle_event *mygpio_recv_idle_event(struct t_mygpio_connection *connection) { + unsigned gpio; + enum mygpio_event event; + uint64_t timestamp; + + struct t_mygpio_pair *pair; + if ((pair = mygpio_recv_pair_name(connection, "gpio")) == NULL) { + return NULL; + } + if (mygpio_parse_uint(pair->value, &gpio, NULL, 0, GPIOS_MAX) == false) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "event")) == NULL) { + return NULL; + } + if ((event = mygpio_parse_event(pair->value)) == MYGPIO_EVENT_UNKNOWN) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + if ((pair = mygpio_recv_pair_name(connection, "timestamp_ms")) == NULL) { + return NULL; + } + if (mygpio_parse_uint64(pair->value, ×tamp, NULL, 0, UINT64_MAX) == false) { + mygpio_free_pair(pair); + return NULL; + } + mygpio_free_pair(pair); + + struct t_mygpio_idle_event *gpio_event = malloc(sizeof(struct t_mygpio_idle_event)); + assert(gpio_event); + gpio_event->gpio = gpio; + gpio_event->event = event; + gpio_event->timestamp_ms = timestamp; + return gpio_event; +} + +/** + * Returns the GPIO number from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return GPIO number. + */ +unsigned mygpio_idle_event_get_gpio(struct t_mygpio_idle_event *event) { + return event->gpio; +} + +/** + * Returns the event type from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The event type, one of enum mygpio_event. + */ +enum mygpio_event mygpio_idle_event_get_event(struct t_mygpio_idle_event *event) { + return event->event; +} + +/** + * Returns the event type name from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The event type name + */ +const char *mygpio_idle_event_get_event_name(struct t_mygpio_idle_event *event) { + return mygpio_lookup_event(event->event); +} + +/** + * Returns the timestamp from an idle event. + * @param event Pointer to struct t_mygpio_idle_event. + * @return The timestamp in milliseconds. + */ +uint64_t mygpio_idle_event_get_timestamp_ms(struct t_mygpio_idle_event *event) { + return event->timestamp_ms; +} + +/** + * Frees the idle event struct + * @param event struct to free + */ +void mygpio_free_idle_event(struct t_mygpio_idle_event *event) { + free(event); +} diff --git a/dist/myGPIOd/libmygpio/src/idle.h b/dist/myGPIOd/libmygpio/src/idle.h new file mode 100644 index 000000000..450e1059a --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/idle.h @@ -0,0 +1,21 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_IDLE_H +#define LIBMYGPIO_SRC_IDLE_H + +#include "libmygpio/include/libmygpio/libmygpio_idle.h" + +/** + * Struct holding the event information received by mygpio_recv_idle_event. + */ +struct t_mygpio_idle_event { + unsigned gpio; //!< GPIO number + enum mygpio_event event; //!< the event + uint64_t timestamp_ms; //!< timestamp in milliseconds +}; + +#endif diff --git a/dist/myGPIOd/libmygpio/src/pair.c b/dist/myGPIOd/libmygpio/src/pair.c new file mode 100644 index 000000000..049e0423b --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/pair.c @@ -0,0 +1,91 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/connection.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/socket.h" + +#include +#include +#include + +// private definitions + +static struct t_mygpio_pair *libmygpio_parse_pair(const char *line); + +// public functions + +/** + * Receives and parses the key/value pair from the response. + * @param connection connection struct + * @return the pair or NULL on error or response end + */ +struct t_mygpio_pair *mygpio_recv_pair(struct t_mygpio_connection *connection) { + if (mygpio_connection_check(connection) == false) { + return NULL; + } + libmygpio_socket_recv_line(connection->fd, &connection->buf_in, 0); + if (connection->buf_in.len == 0) { + return NULL; + } + if (strcmp(connection->buf_in.buffer, "END") == 0) { + return NULL; + } + return libmygpio_parse_pair(connection->buf_in.buffer); +} + +/** + * Receives and parses the key/value pair from the response. + * It checks the name. + * @param connection connection struct + * @param name desired name of the pair + * @return the pair or NULL on error or response end + */ +struct t_mygpio_pair *mygpio_recv_pair_name(struct t_mygpio_connection *connection, const char *name) { + struct t_mygpio_pair *pair = mygpio_recv_pair(connection); + if (pair == NULL) { + return NULL; + } + if (strcmp(pair->name, name) != 0) { + mygpio_free_pair(pair); + return NULL; + } + return pair; +} + +/** + * Frees the name/value pair + * @param pair pair to free + */ +void mygpio_free_pair(struct t_mygpio_pair *pair) { + pair->name = NULL; + pair->value = NULL; + free(pair); +} + +// private functions + +/** + * Parses a line to a key/value pair. + * Name and value are only pointers and are not copied. + * @param line line to parse + * @return allocated pair or NULL on error + */ +static struct t_mygpio_pair *libmygpio_parse_pair(const char *line) { + struct t_mygpio_pair *pair = malloc(sizeof(struct t_mygpio_pair)); + if (pair == NULL) { + return NULL; + } + char *p = strchr(line, ':'); + if (p == NULL) { + mygpio_free_pair(pair); + return NULL; + } + pair->name = line; + *p = '\0'; + pair->value = p + 1; + return pair; +} diff --git a/dist/myGPIOd/libmygpio/src/pair.h b/dist/myGPIOd/libmygpio/src/pair.h new file mode 100644 index 000000000..ad9758587 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/pair.h @@ -0,0 +1,24 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef MYGPIO_SRC_PAIR_H +#define MYGPIO_SRC_PAIR_H + +struct t_mygpio_connection; + +/** + * Key/Value pair + */ +struct t_mygpio_pair { + const char *name; //!< pointer to name + const char *value; //!< pointer to value +}; + +struct t_mygpio_pair *mygpio_recv_pair(struct t_mygpio_connection *connection); +struct t_mygpio_pair *mygpio_recv_pair_name(struct t_mygpio_connection *connection, const char *name); +void mygpio_free_pair(struct t_mygpio_pair *pair); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/parser.c b/dist/myGPIOd/libmygpio/src/parser.c new file mode 100644 index 000000000..6effe5b0a --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/parser.c @@ -0,0 +1,230 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" +#include "libmygpio/include/libmygpio/libmygpio_gpio.h" + +#include +#include +#include +#include + +/** + * Lookups the name for the gpio direction. + * @param direction the gpio direction. + * @return gpio direction name + */ +const char *mygpio_gpio_lookup_direction(enum mygpio_gpio_direction direction) { + switch(direction) { + case MYGPIO_GPIO_DIRECTION_IN: + return "in"; + case MYGPIO_GPIO_DIRECTION_OUT: + return "out"; + case MYGPIO_GPIO_DIRECTION_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to the gpio direction. + * @param str string to parse + * @return direction of the gpio + */ +enum mygpio_gpio_direction mygpio_gpio_parse_direction(const char *str) { + if (strcmp(str, "in") == 0) { + return MYGPIO_GPIO_DIRECTION_IN; + } + if (strcmp(str, "out") == 0) { + return MYGPIO_GPIO_DIRECTION_OUT; + } + return MYGPIO_GPIO_DIRECTION_UNKNOWN; +} + +/** + * Lookups the name for the gpio value. + * @param value the gpio value. + * @return gpio value name + */ +const char *mygpio_gpio_lookup_value(enum mygpio_gpio_value value) { + switch(value) { + case MYGPIO_GPIO_VALUE_ACTIVE: + return "active"; + case MYGPIO_GPIO_VALUE_INACTIVE: + return "inactive"; + case MYGPIO_GPIO_VALUE_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to a gpio value. + * @param str string to parse + * @return gpio value or GPIO_VALUE_LOW on error + */ +enum mygpio_gpio_value mygpio_gpio_parse_value(const char *str) { + if (strcasecmp(str, "active") == 0) { + return MYGPIO_GPIO_VALUE_ACTIVE; + } + if (strcasecmp(str, "inactive") == 0) { + return MYGPIO_GPIO_VALUE_INACTIVE; + } + return MYGPIO_GPIO_VALUE_UNKNOWN; +} + +/** + * Lookups the name for the gpio bias. + * @param bias the gpio bias. + * @return gpio bias name + */ +const char *mygpio_gpio_lookup_bias(enum mygpio_gpio_bias bias) { + switch(bias) { + case MYGPIO_BIAS_AS_IS: + return "as-is"; + case MYGPIO_BIAS_DISABLED: + return "disable"; + case MYGPIO_BIAS_PULL_DOWN: + return "pull-down"; + case MYGPIO_BIAS_PULL_UP: + return "pull-up"; + case MYGPIO_EVENT_REQUEST_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to a gpio bias. + * @param str string to parse + * @return gpio bias or GPIO_BIAS_UNKNOWN on error + */ +enum mygpio_gpio_bias mygpio_gpio_parse_bias(const char *str) { + if (strcasecmp(str, "as-is") == 0) { + return MYGPIO_BIAS_AS_IS; + } + if (strcasecmp(str, "disable") == 0) { + return MYGPIO_BIAS_DISABLED; + } + if (strcasecmp(str, "pull-down") == 0) { + return MYGPIO_BIAS_PULL_DOWN; + } + if (strcasecmp(str, "pull-up") == 0) { + return MYGPIO_BIAS_PULL_UP; + } + return MYGPIO_BIAS_UNKNOWN; +} + +/** + * Lookups the name for an event request. + * @param value the gpio event request. + * @return gpio value name + */ +const char *mygpio_gpio_lookup_event_request(enum mygpio_event_request event_request) { + switch(event_request) { + case MYGPIO_EVENT_REQUEST_FALLING: + return "falling"; + case MYGPIO_EVENT_REQUEST_RISING: + return "rising"; + case MYGPIO_EVENT_REQUEST_BOTH: + return "both"; + case MYGPIO_EVENT_REQUEST_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to an event request. + * @param str string to parse + * @return gpio event request or GPIO_EVENT_REQUEST_UNKNOWN on error + */ +enum mygpio_event_request mygpio_gpio_parse_event_request(const char *str) { + if (strcasecmp(str, "falling") == 0) { + return MYGPIO_EVENT_REQUEST_FALLING; + } + if (strcasecmp(str, "rising") == 0) { + return MYGPIO_EVENT_REQUEST_RISING; + } + if (strcasecmp(str, "both") == 0) { + return MYGPIO_EVENT_REQUEST_BOTH; + } + return MYGPIO_EVENT_REQUEST_UNKNOWN; +} + +/** + * Lookups the name for the gpio event clock. + * @param clock the gpio clock. + * @return gpio clock name + */ +const char *mygpio_gpio_lookup_event_clock(enum mygpio_event_clock clock) { + switch(clock) { + case MYGPIO_EVENT_CLOCK_MONOTONIC: + return "monotonic"; + case MYGPIO_EVENT_CLOCK_REALTIME: + return "realtime"; + case MYGPIO_EVENT_CLOCK_HTE: + return "hte"; + case MYGPIO_EVENT_CLOCK_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to a gpio event clock. + * @param str string to parse + * @return gpio event clock or MYGPIO_EVENT_CLOCK_UNKNOWN on error + */ +enum mygpio_event_clock mygpio_gpio_parse_event_clock(const char *str) { + if (strcasecmp(str, "monotonic") == 0) { + return MYGPIO_EVENT_CLOCK_MONOTONIC; + } + if (strcasecmp(str, "realtime") == 0) { + return MYGPIO_EVENT_CLOCK_REALTIME; + } + if (strcasecmp(str, "hte") == 0) { + return MYGPIO_EVENT_CLOCK_HTE; + } + return MYGPIO_EVENT_CLOCK_UNKNOWN; +} + +/** + * Lookups the name for the gpio drive setting. + * @param drive the gpio drive. + * @return gpio drive name + */ +const char *mygpio_gpio_lookup_drive(enum mygpio_drive drive) { + switch(drive) { + case MYGPIO_DRIVE_PUSH_PULL: + return "push-pull"; + case MYGPIO_DRIVE_OPEN_DRAIN: + return "open-drain"; + case MYGPIO_DRIVE_OPEN_SOURCE: + return "open-source"; + case MYGPIO_DRIVE_UNKNOWN: + return "unknown"; + } + return "unknown"; +} + +/** + * Parses a string to a gpio drive. + * @param str string to parse + * @return gpio bias or MYGPIO_DRIVE_UNKNOWN on error + */ +enum mygpio_drive mygpio_gpio_parse_drive(const char *str) { + if (strcasecmp(str, "push-pull") == 0) { + return MYGPIO_DRIVE_PUSH_PULL; + } + if (strcasecmp(str, "open-drain") == 0) { + return MYGPIO_DRIVE_OPEN_DRAIN; + } + if (strcasecmp(str, "open-source") == 0) { + return MYGPIO_DRIVE_OPEN_SOURCE; + } + return MYGPIO_DRIVE_UNKNOWN; +} diff --git a/dist/myGPIOd/libmygpio/src/protocol.c b/dist/myGPIOd/libmygpio/src/protocol.c new file mode 100644 index 000000000..eb75a7e7d --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/protocol.c @@ -0,0 +1,154 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/protocol.h" + +#include "libmygpio/src/connection.h" +#include "libmygpio/src/pair.h" +#include "libmygpio/src/socket.h" +#include "libmygpio/src/util.h" +#include "mygpio-common/util.h" + +#include +#include +#include +#include +#include + +// private definitions + +static bool libmygpio_parse_version(const char *str, struct t_mygpio_connection *connection); + +// public functions + +/** + * Writes the format string to the output buffer and writes it to the socket. + * @param connection connection struct + * @param fmt format string + * @param ... variadic arguments for the format string + * @return true on success, else false + */ +bool libmygpio_send_line(struct t_mygpio_connection *connection, const char *fmt, ...) { + if (mygpio_connection_check(connection) == false) { + return false; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf(connection->buf_out.buffer, BUFFER_SIZE_MAX, fmt, args); + va_end(args); + if (written <= 0 || + written >= BUFFER_SIZE_MAX) + { + libmygpio_buf_init(&connection->buf_out); + libmygpio_connection_set_state(connection, MYGPIO_STATE_FATAL, "Buffer write error"); + return false; + } + connection->buf_out.len = (size_t)written; + bool rc = libmygpio_socket_send_line(connection->fd, &connection->buf_out); + if (rc == false) { + libmygpio_connection_set_state(connection, MYGPIO_STATE_FATAL, "Socket write error"); + } + return rc; +} + +/** + * Checks the response status. Populates the connection error buffer + * @param connection connection struct + * @return true on success, else false + */ +bool libmygpio_recv_response_status(struct t_mygpio_connection *connection) { + if (mygpio_connection_check(connection) == false || + libmygpio_socket_recv_line(connection->fd, &connection->buf_in, connection->timeout_ms) == false) + { + LIBMYGPIO_LOG("Error receiving line"); + libmygpio_connection_set_state(connection, MYGPIO_STATE_FATAL, "Error receiving line"); + return false; + } + if (strcmp(connection->buf_in.buffer, "OK") == 0) { + return true; + } + if (connection->error != NULL) { + free(connection->error); + connection->error = NULL; + } + if (strncmp(connection->buf_in.buffer, "ERROR:", 6) == 0) { + char *p = strchr(connection->buf_in.buffer, ':'); + p++; + libmygpio_connection_set_state(connection, MYGPIO_STATE_ERROR, p); + } + else { + libmygpio_connection_set_state(connection, MYGPIO_STATE_ERROR, "Malformed server response"); + } + return false; +} + +/** + * Receives and parses the version from myGPIOd connect handshake + * @param connection connection struct + * @return true on success, else false + */ +bool libmygpio_recv_version(struct t_mygpio_connection *connection) { + struct t_mygpio_pair *pair = mygpio_recv_pair(connection); + if (pair == NULL) { + LIBMYGPIO_LOG("Could not receive version pair"); + return false; + } + if (strcmp(pair->name, "version") != 0 || + libmygpio_parse_version(pair->value, connection) == false) + { + LIBMYGPIO_LOG("Invalid version pair"); + mygpio_free_pair(pair); + return false; + } + mygpio_free_pair(pair); + return true; +} + +/** + * Finish reading the server response + * @param connection connection struct + * @return true on success, else false + */ +bool mygpio_response_end(struct t_mygpio_connection *connection) { + if (mygpio_connection_check(connection) == false) { + return false; + } + while (libmygpio_socket_recv_line(connection->fd, &connection->buf_in, 0) == true) { + if (strcmp(connection->buf_in.buffer, "END") == 0) { + libmygpio_buf_init(&connection->buf_in); + return true; + } + } + libmygpio_buf_init(&connection->buf_in); + return false; +} + +// private functions + +/** + * Parses the myGPIOd version string to major.minor.patch + * @param str string to parse + * @param connection connection to populate the version + * @return true on success, else false + */ +static bool libmygpio_parse_version(const char *str, struct t_mygpio_connection *connection) { + char *rest; + if (mygpio_parse_uint(str, &connection->version[0], &rest, 0, 99) == false) { + return false; + } + if (*rest != '.') { return false; } + rest++; + if (mygpio_parse_uint(rest, &connection->version[1], &rest, 0, 99) == false) { + return false; + } + if (*rest != '.') { return false; } + rest++; + if (mygpio_parse_uint(rest, &connection->version[2], &rest, 0, 99) == false) { + return false; + } + if (*rest != '\0') { return false; } + return true; +} diff --git a/dist/myGPIOd/libmygpio/src/protocol.h b/dist/myGPIOd/libmygpio/src/protocol.h new file mode 100644 index 000000000..710c3e3ef --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/protocol.h @@ -0,0 +1,18 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_PROTOCOL_H +#define LIBMYGPIO_SRC_PROTOCOL_H + +#include "libmygpio/src/connection.h" + +#include + +bool libmygpio_send_line(struct t_mygpio_connection *connection, const char *fmt, ...); +bool libmygpio_recv_version(struct t_mygpio_connection *connection); +bool libmygpio_recv_response_status(struct t_mygpio_connection *connection); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/socket.c b/dist/myGPIOd/libmygpio/src/socket.c new file mode 100644 index 000000000..4163476e2 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/socket.c @@ -0,0 +1,126 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/socket.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Connects to a socket + * @param socket_path unix socket to connect + * @return open file descriptor + */ +int libmygpio_socket_connect(const char *socket_path) { + struct sockaddr_un address = { 0 }; + address.sun_family = AF_UNIX; + strncpy(address.sun_path, socket_path, 108); + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return -1; + } + + int flags = fcntl(fd, F_GETFD, 0); + if (fcntl(fd, F_SETFD, flags | O_CLOEXEC)) { + close(fd); + return -1; + } + + if (connect(fd, (struct sockaddr *)&address, sizeof(address)) == -1) { + close(fd); + return -1; + } + return fd; +} + +/** + * Closes the file descriptor + * @param fd file descriptor + */ +void libmygpio_socket_close(int fd) { + if (fd > 0) { + close(fd); + } +} + +/** + * Receives a line from the socket and crops the ending LF. + * This command blocks. + * @param fd file descriptor to read + * @param buf buffer to fill + * @param timeout_ms timeout in ms + * 0 for no wait + * -1 for no timeout + * @return true on success, else false + */ +bool libmygpio_socket_recv_line(int fd, struct t_buf *buf, int timeout_ms) { + libmygpio_buf_init(buf); + ssize_t nread; + int flag = 0; + + if (timeout_ms == 0) { + flag = MSG_DONTWAIT; + } + else if (timeout_ms > 0) { + struct pollfd pfds[1]; + pfds[0].fd = fd; + pfds[0].events = POLLIN; + if (poll(pfds, 1, timeout_ms) <= 0) { + return false; + } + } + + while ((nread = recv(fd, buf->buffer + buf->len, 1, flag)) > 0) { + buf->len += (size_t)nread; + if (buf->buffer[buf->len - 1] == '\n') { + buf->len--; + buf->buffer[buf->len] = '\0'; + return true; + } + if (buf->len == BUFFER_SIZE_MAX) { + break; + } + } + buf->buffer[buf->len - 1] = '\0'; + return false; +} + +/** + * Writes a line to the socket. + * Buffer will be cleared. + * @param fd socket to write + * @param buf buffer to write + * @return true on success, else false + */ +bool libmygpio_socket_send_line(int fd, struct t_buf *buf) { + ssize_t nwrite; + size_t written = 0; + size_t max_bytes = buf->len; + while ((nwrite = write(fd, buf->buffer + written, max_bytes)) > 0) { + if (nwrite < 0) { + libmygpio_buf_init(buf); + return false; + } + written += (size_t)nwrite; + max_bytes = buf->len - written; + if (written == buf->len) { + libmygpio_buf_init(buf); + if (write(fd, "\n", 1) != 1) { + return false; + } + return true; + } + } + libmygpio_buf_init(buf); + return false; +} diff --git a/dist/myGPIOd/libmygpio/src/socket.h b/dist/myGPIOd/libmygpio/src/socket.h new file mode 100644 index 000000000..ae11abbad --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/socket.h @@ -0,0 +1,19 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + libmygpio (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_SOCKET_H +#define LIBMYGPIO_SRC_SOCKET_H + +#include "libmygpio/src/buffer.h" + +#include + +int libmygpio_socket_connect(const char *socket_path); +void libmygpio_socket_close(int fd); +bool libmygpio_socket_recv_line(int fd, struct t_buf *buf, int timeout_ms); +bool libmygpio_socket_send_line(int fd, struct t_buf *buf); + +#endif diff --git a/dist/myGPIOd/libmygpio/src/util.c b/dist/myGPIOd/libmygpio/src/util.c new file mode 100644 index 000000000..ea68a05a4 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/util.c @@ -0,0 +1,35 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "libmygpio/src/util.h" + +#include +#include +#include +#include +#include +#include + +#ifdef MYGPIOD_DEBUG +/** + * Debug logging function. Do not call it directly, use the LIBMYGPIO_LOG macro. + * @param file file of the log statement + * @param line line number of the log statement + * @param fmt format string + * @param ... variadic arguments for format string + */ +void libmygpio_log_log(const char *file, int line, const char *fmt, ...) { + printf("%s:%d: ", file, line); + va_list args; + va_start(args, fmt); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-nonliteral" + vprintf(fmt, args); + va_end(args); + #pragma GCC diagnostic pop + printf("\n"); +} +#endif diff --git a/dist/myGPIOd/libmygpio/src/util.h b/dist/myGPIOd/libmygpio/src/util.h new file mode 100644 index 000000000..9d2db5387 --- /dev/null +++ b/dist/myGPIOd/libmygpio/src/util.h @@ -0,0 +1,20 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef LIBMYGPIO_SRC_UTIL_H +#define LIBMYGPIO_SRC_UTIL_H + +#include +#include + +#ifdef MYGPIOD_DEBUG + #define LIBMYGPIO_LOG(...) libmygpio_log_log(__FILE__, __LINE__, __VA_ARGS__) + void libmygpio_log_log(const char *file, int line, const char *fmt, ...); +#else + #define LIBMYGPIO_LOG(...) +#endif + +#endif diff --git a/dist/myGPIOd/mygpio-common/CMakeLists.txt b/dist/myGPIOd/mygpio-common/CMakeLists.txt new file mode 100644 index 000000000..6ccfe5618 --- /dev/null +++ b/dist/myGPIOd/mygpio-common/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# myMPD (c) 2020-2024 Juergen Mang +# https://github.com/jcorporation/mympd + +add_library(mygpio-common "") + +target_sources(mygpio-common + PRIVATE + util.c +) + +target_include_directories(mygpio-common + PRIVATE + ${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR} +) diff --git a/dist/myGPIOd/mygpio-common/compile_commands.json b/dist/myGPIOd/mygpio-common/compile_commands.json new file mode 100644 index 000000000..43f451588 --- /dev/null +++ b/dist/myGPIOd/mygpio-common/compile_commands.json @@ -0,0 +1,296 @@ +[ +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpio-common", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio-common.dir/util.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpio-common/util.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpio-common/util.c", + "output": "mygpio-common/CMakeFiles/mygpio-common.dir/util.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/buffer.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/buffer.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/buffer.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/buffer.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/connection.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/connection.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/connection.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/connection.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/gpio_struct.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpio_struct.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpio_struct.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/gpio_struct.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/gpio.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpio.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpio.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/gpio.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/gpioinfo.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpioinfo.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpioinfo.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/gpioinfo.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/gpiolist.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpiolist.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/gpiolist.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/gpiolist.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/idle.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/idle.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/idle.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/idle.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/parser.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/parser.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/parser.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/parser.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/pair.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/pair.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/pair.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/pair.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/protocol.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/protocol.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/protocol.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/protocol.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/socket.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/socket.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/socket.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/socket.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio", + "command": "/usr/bin/cc -Dmygpio_EXPORTS -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -fPIC -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio.dir/src/util.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/util.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/src/util.c", + "output": "libmygpio/CMakeFiles/mygpio.dir/src/util.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/libmygpio/example", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpio-example.dir/main.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/example/main.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/libmygpio/example/main.c", + "output": "libmygpio/example/CMakeFiles/mygpio-example.dir/main.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpioc", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpioc.dir/main.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/main.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/main.c", + "output": "mygpioc/CMakeFiles/mygpioc.dir/main.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpioc", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpioc.dir/gpio.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/gpio.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/gpio.c", + "output": "mygpioc/CMakeFiles/mygpioc.dir/gpio.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpioc", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpioc.dir/idle.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/idle.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/idle.c", + "output": "mygpioc/CMakeFiles/mygpioc.dir/idle.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpioc", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpioc.dir/options.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/options.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/options.c", + "output": "mygpioc/CMakeFiles/mygpioc.dir/options.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpioc", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpioc.dir/util.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/util.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpioc/util.c", + "output": "mygpioc/CMakeFiles/mygpioc.dir/util.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/main.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/main.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/main.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/main.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/gpio.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/gpio.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/gpio.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/gpio.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/system.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/system.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/system.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/system.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/event_loop/event_loop.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/event_loop/event_loop.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/event_loop/event_loop.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/event_loop/event_loop.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/event_loop/signal_handler.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/event_loop/signal_handler.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/event_loop/signal_handler.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/event_loop/signal_handler.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/action.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/action.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/action.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/action.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/chip.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/chip.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/chip.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/chip.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/event.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/event.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/event.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/event.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/gpio.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/gpio.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/gpio.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/gpio.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/input.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/input.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/input.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/input.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/output.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/output.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/output.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/output.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/gpio/timer.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/timer.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/gpio/timer.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/gpio/timer.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/action.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/action.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/action.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/action.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/config.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/config.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/config.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/config.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/events.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/events.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/events.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/events.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/list.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/list.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/list.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/list.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/log.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/log.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/log.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/log.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/timer.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/timer.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/timer.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/timer.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/lib/util.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/util.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/lib/util.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/lib/util.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/gpio.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/gpio.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/gpio.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/gpio.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/idle.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/idle.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/idle.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/idle.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/protocol.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/protocol.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/protocol.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/protocol.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/response.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/response.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/response.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/response.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/socket.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/socket.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/socket.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/socket.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/server/event.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/event.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/server/event.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/server/event.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/mpc.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/mpc.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/mpc.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/mpc.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/http.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/http.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/http.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/http.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/mympd.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/mympd.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/mympd.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/mympd.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/mygpiod", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd/debug -I/home/juergen/projekte/myGPIOd/myGPIOd -I/home/juergen/projekte/myGPIOd/myGPIOd/debug/include -I/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod -I/usr/include/lua5.4 -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -o CMakeFiles/mygpiod.dir/actions/lua.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/lua.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/mygpiod/actions/lua.c", + "output": "mygpiod/CMakeFiles/mygpiod.dir/actions/lua.c.o" +}, +{ + "directory": "/home/juergen/projekte/myGPIOd/myGPIOd/debug/dist/sds", + "command": "/usr/bin/cc -I/home/juergen/projekte/myGPIOd/myGPIOd -DMYGPIOD_DEBUG=ON -fdata-sections -ffunction-sections -fstack-protector-strong -pedantic -Wall -Wconversion -Werror -Wextra -Wformat -Wformat=2 -Wformat-security -Winit-self -Wmissing-include-dirs -Wnested-externs -Wold-style-definition -Wredundant-decls -Wshadow -Wsign-compare -Wstrict-prototypes -Wundef -Wuninitialized -Wunused-parameter -Wvla -Wwrite-strings -std=gnu17 -fstack-clash-protection -fcf-protection -fno-plt -D_FORTIFY_SOURCE=2 -ggdb -Og -Wno-conversion -o CMakeFiles/sds.dir/sds.c.o -c /home/juergen/projekte/myGPIOd/myGPIOd/dist/sds/sds.c", + "file": "/home/juergen/projekte/myGPIOd/myGPIOd/dist/sds/sds.c", + "output": "dist/sds/CMakeFiles/sds.dir/sds.c.o" +} +] \ No newline at end of file diff --git a/dist/myGPIOd/mygpio-common/compile_time.h.in b/dist/myGPIOd/mygpio-common/compile_time.h.in new file mode 100644 index 000000000..47584a0eb --- /dev/null +++ b/dist/myGPIOd/mygpio-common/compile_time.h.in @@ -0,0 +1,42 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/myGPIOd +*/ + +#ifndef COMPILE_TIME_H +#define COMPILE_TIME_H + +// Version and name from cmake +#define MYGPIO_VERSION "${PROJECT_VERSION}" +#define MYGPIOD_NAME "myGPIOd" +#define MYGPIOC_NAME "myGPIOc" + +// Sanitizers +#cmakedefine MYGPIOD_ENABLE_ASAN +#cmakedefine MYGPIOD_ENABLE_TSAN +#cmakedefine MYGPIOD_ENABLE_UBSAN + +// Features +#cmakedefine MYGPIOD_ENABLE_ACTION_MPC +#cmakedefine MYGPIOD_ENABLE_ACTION_HTTP +#cmakedefine MYGPIOD_ENABLE_ACTION_LUA + +// Default configuration +#define CFG_CHIP "/dev/gpiochip0" +#define CFG_LOGLEVEL LOG_NOTICE +#define CFG_SYSLOG false +#define CFG_GPIO_DIR "/etc/mygpiod.d" +#define CFG_SOCKET_PATH "/run/mygpiod/socket" +#define CFG_SOCKET_TIMEOUT 60 //seconds + +// Other defaults +#define CLIENT_CONNECTIONS_MAX 10 +#define GPIOS_MAX 64 +#define LINE_LENGTH_MAX 1024 +#define WAITING_EVENTS_MAX 10 +#define GPIO_EVENT_BUF_SIZE 32 +#define OPEN_FLAGS_READ "re" +#define TIMEOUT_MS_MAX 9999 + +#endif diff --git a/dist/myGPIOd/mygpio-common/util.c b/dist/myGPIOd/mygpio-common/util.c new file mode 100644 index 000000000..407c4d277 --- /dev/null +++ b/dist/myGPIOd/mygpio-common/util.c @@ -0,0 +1,226 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#include "compile_time.h" +#include "mygpio-common/util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Parses a string to an integer value and checks it against min and max. + * If rest is specified as NULL the complete string must be a number. + * @param str string to parse + * @param result pointer for the result + * @param rest pointer to first none numeric char + * @param min minimum value (including) + * @param max maximum value (including) + * @return bool true on success, else false + */ +bool mygpio_parse_int(const char *str, int *result, char **rest, int min, int max) { + if (str == NULL || + str[0] == '\0' || + isspace(str[0])) + { + return false; + } + errno = 0; + char *endptr; + long v = strtol(str, &endptr, 10); + if (v > INT_MAX || + v < INT_MIN) + { + return false; + } + if (errno == 0 && // no error returned + endptr != str && // parsed some chars + v >= min && // enforce limit + v <= max) // enforce limit + { + if (rest == NULL) { + // strict mode + if (*endptr != '\0') { + return false; + } + } + else { + *rest = endptr; + } + *result = (int)v; + return true; + } + return false; +} + +/** + * Parses a string to an unsigned value and checks it against min and max. + * If rest is specified as NULL the complete string must be a number. + * @param str string to parse + * @param result pointer for the result + * @param rest pointer to first none numeric char + * @param min minimum value (including) + * @param max maximum value (including) + * @return bool true on success, else false + */ +bool mygpio_parse_uint(const char *str, unsigned *result, char **rest, unsigned min, unsigned max) { + if (str == NULL || + str[0] == '\0' || + isspace(str[0])) + { + return false; + } + if (str[0] == '-') { + return false; + } + errno = 0; + char *endptr; + unsigned long v = strtoul(str, &endptr, 10); + if (v > UINT_MAX) { + return false; + } + if (errno == 0 && // no error returned + endptr != str && // parsed some chars + v >= min && // enforce limit + v <= max) // enforce limit + { + if (rest == NULL) { + // strict mode + if (*endptr != '\0') { + return false; + } + } + else { + *rest = endptr; + } + *result = (unsigned)v; + return true; + } + return false; +} + +/** + * Parses a string to an unsigned long value and checks it against min and max. + * If rest is specified as NULL the complete string must be a number. + * @param str string to parse + * @param result pointer for the result + * @param rest pointer to first none numeric char + * @param min minimum value (including) + * @param max maximum value (including) + * @return bool true on success, else false + */ +bool mygpio_parse_ulong(const char *str, unsigned long *result, char **rest, unsigned long min, unsigned long max) { + if (str == NULL || + str[0] == '\0' || + isspace(str[0])) + { + return false; + } + if (str[0] == '-') { + return false; + } + errno = 0; + char *endptr; + unsigned long v = strtoul(str, &endptr, 10); + if (errno == 0 && // no error returned + endptr != str && // parsed some chars + v >= min && // enforce limit + v <= max) // enforce limit + { + if (rest == NULL) { + // strict mode + if (*endptr != '\0') { + return false; + } + } + else { + *rest = endptr; + } + *result = (unsigned)v; + return true; + } + return false; +} + +/** + * Parses the start of a string to an uint64_t value. + * @param str string to parse + * @param result pointer for the result + * @param rest pointer to first none numeric char + * @param min minimum value (including) + * @param max maximum value (including) + * @return bool true on success, else false + */ +bool mygpio_parse_uint64(const char *str, uint64_t *result, char **rest, uint64_t min, uint64_t max) { + if (str == NULL || + str[0] == '\0' || + isspace(str[0])) + { + return false; + } + if (str[0] == '-') { + return false; + } + errno = 0; + char *endptr; + unsigned long long v = strtoull(str, &endptr, 10); + if (errno == 0 && // no error returned + endptr != str && // parsed some chars + v >= min && // enforce limit + v <= max) // enforce limit + { + if (rest == NULL) { + // strict mode + if (*endptr != '\0') { + return false; + } + } + else { + *rest = endptr; + } + *result = (uint64_t)v; + return true; + } + return false; +} + +/** + * Parses a string to a boolean value. + * Sets errno to EINVAL on parser error. + * Returns false if string is not true. + * @param str string to parse + * @return parsed value + */ +bool mygpio_parse_bool(const char *str) { + if (strcasecmp(str, "true") == 0 || + strcmp(str, "1") == 0) + { + return true; + } + if (strcasecmp(str, "false") == 0 || + strcmp(str, "0") == 0) + { + return false; + } + errno = EINVAL; + return false; +} + +/** + * Prints a bool value as string + * @param v the bool value + * @return string + */ +const char *mygpio_bool_to_str(bool v) { + return v == true + ? "true" + : "false"; +} diff --git a/dist/myGPIOd/mygpio-common/util.h b/dist/myGPIOd/mygpio-common/util.h new file mode 100644 index 000000000..d80cf0a3c --- /dev/null +++ b/dist/myGPIOd/mygpio-common/util.h @@ -0,0 +1,19 @@ +/* + SPDX-License-Identifier: GPL-3.0-or-later + myGPIOd (c) 2020-2024 Juergen Mang + https://github.com/jcorporation/mympd +*/ + +#ifndef MYGPIO_COMMON_UTIL_H +#define MYGPIO_COMMON_UTIL_H + +#include +#include + +bool mygpio_parse_int(const char *str, int *result, char **rest, int min, int max); +bool mygpio_parse_uint(const char *str, unsigned *result, char **rest, unsigned min, unsigned max); +bool mygpio_parse_ulong(const char *str, unsigned long *result, char **rest, unsigned long min, unsigned long max); +bool mygpio_parse_uint64(const char *str, uint64_t *result, char **rest, uint64_t min, uint64_t max); +bool mygpio_parse_bool(const char *str); +const char *mygpio_bool_to_str(bool v); +#endif diff --git a/dist/utest/utest.h b/dist/utest/utest.h index 073041a22..5dc25b14c 100644 --- a/dist/utest/utest.h +++ b/dist/utest/utest.h @@ -321,7 +321,7 @@ static UTEST_INLINE void *utest_realloc(void *const pointer, size_t new_size) { void *const new_pointer = realloc(pointer, new_size); if (UTEST_NULL == new_pointer) { - free(new_pointer); + free(pointer); } return new_pointer; @@ -512,6 +512,10 @@ template <> struct utest_type_deducer { static void _(const unsigned long long i) { UTEST_PRINTF("%llu", i); } }; +template <> struct utest_type_deducer { + static void _(const bool i) { UTEST_PRINTF(i ? "true" : "false"); } +}; + template struct utest_type_deducer { static void _(const T *t) { UTEST_PRINTF("%p", static_cast(const_cast(t))); @@ -528,6 +532,12 @@ template struct utest_type_deducer { } }; +template <> struct utest_type_deducer { + static void _(std::nullptr_t t) { + UTEST_PRINTF("%p", static_cast(t)); + } +}; + template UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const T t) { utest_type_deducer::_(t); @@ -724,10 +734,12 @@ utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { UTEST_AUTO(x) xEval = (x); \ UTEST_AUTO(y) yEval = (y); \ if (!((xEval)cond(yEval))) { \ + const char *const xAsString = #x; \ + const char *const yAsString = #y; \ _Pragma("clang diagnostic pop") \ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ UTEST_PRINTF(" Expected : ("); \ - UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF("%s) " #cond " (%s", xAsString, yAsString); \ UTEST_PRINTF(")\n"); \ UTEST_PRINTF(" Actual : "); \ utest_type_printer(xEval); \ @@ -751,9 +763,11 @@ utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { UTEST_AUTO(x) xEval = (x); \ UTEST_AUTO(y) yEval = (y); \ if (!((xEval)cond(yEval))) { \ + const char *const xAsString = #x; \ + const char *const yAsString = #y; \ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ UTEST_PRINTF(" Expected : ("); \ - UTEST_PRINTF(#x ") " #cond " (" #y); \ + UTEST_PRINTF("%s) " #cond " (%s", xAsString, yAsString); \ UTEST_PRINTF(")\n"); \ UTEST_PRINTF(" Actual : "); \ utest_type_printer(xEval); \ @@ -1138,13 +1152,19 @@ utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ sizeof(struct utest_test_state_s) * \ utest_state.tests_length)); \ - if (utest_state.tests) { \ + if (utest_state.tests && name) { \ utest_state.tests[index].func = &utest_##SET##_##NAME; \ utest_state.tests[index].name = name; \ utest_state.tests[index].index = 0; \ UTEST_SNPRINTF(name, name_size, "%s", name_part); \ - } else if (name) { \ - free(name); \ + } else { \ + if (utest_state.tests) { \ + free(utest_state.tests); \ + utest_state.tests = NULL; \ + } \ + if (name) { \ + free(name); \ + } \ } \ } \ UTEST_SURPRESS_WARNINGS_END \ @@ -1186,12 +1206,18 @@ utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ sizeof(struct utest_test_state_s) * \ utest_state.tests_length)); \ - if (utest_state.tests) { \ + if (utest_state.tests && name) { \ utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ utest_state.tests[index].name = name; \ UTEST_SNPRINTF(name, name_size, "%s", name_part); \ - } else if (name) { \ - free(name); \ + } else { \ + if (utest_state.tests) { \ + free(utest_state.tests); \ + utest_state.tests = NULL; \ + } \ + if (name) { \ + free(name); \ + } \ } \ } \ UTEST_SURPRESS_WARNINGS_END \ @@ -1234,14 +1260,20 @@ utest_strncpy_gcc(char *const dst, const char *const src, const size_t size) { utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ sizeof(struct utest_test_state_s) * \ utest_state.tests_length)); \ - if (utest_state.tests) { \ + if (utest_state.tests && name) { \ utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ utest_state.tests[index].index = i; \ utest_state.tests[index].name = name; \ iUp = UTEST_CAST(utest_uint64_t, i); \ UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ - } else if (name) { \ - free(name); \ + } else { \ + if (utest_state.tests) { \ + free(utest_state.tests); \ + utest_state.tests = NULL; \ + } \ + if (name) { \ + free(name); \ + } \ } \ } \ } \ diff --git a/docs/_includes/version b/docs/_includes/version index 73c5170b3..791fc94d8 100644 --- a/docs/_includes/version +++ b/docs/_includes/version @@ -1 +1 @@ -14.1.2 \ No newline at end of file +15.0.0 \ No newline at end of file diff --git a/docs/additional-topics/behind-a-reverse-proxy.md b/docs/additional-topics/behind-a-reverse-proxy.md index c9a0a761c..510d6b3ce 100644 --- a/docs/additional-topics/behind-a-reverse-proxy.md +++ b/docs/additional-topics/behind-a-reverse-proxy.md @@ -18,7 +18,7 @@ The reverse proxy should be configured to: - remove the subdirectory - rewrite location headers -- support websockets for the `/ws/` uri +- support websockets for the `/ws/*` urls In this examples myMPD is proxied behind the path `/mympd`. diff --git a/docs/configuration/configuration-files.md b/docs/configuration/configuration-files.md index 7d9b058e8..47892bfff 100644 --- a/docs/configuration/configuration-files.md +++ b/docs/configuration/configuration-files.md @@ -29,7 +29,7 @@ systemd-run -p DynamicUser=yes -p StateDirectory=mympd -p CacheDirectory=mympd - | acl | string | MYMPD_ACL | | ACL to access the myMPD webserver: [ACL]({{ site.baseurl }}/configuration/acl), allows all hosts in the default configuration | | album_group_tag | string | MYMPD_ALBUM_GROUP_TAG | Date | Additional tag to group albums | | album_mode | string | MYMPD_ALBUM_MODE | adv | Set the album mode: `adv` or `simple` | -| covercache_keep_days | number | MYMPD_COVERCACHE_KEEP_DAYS | 31 | How long to keep images in the covercache, 0 to disable the cache | +| covercache_keep_days | number | MYMPD_COVERCACHE_KEEP_DAYS | 31 | How long to keep images in the covercache; 0 to disable the cache; -1 to disable pruning of the cache. | | http | boolean | MYMPD_HTTP | true | `true` = Enable listening on http_port | | http_host | string | MYMPD_HTTP_HOST | `[::]` | IP address to listen on, use `[::]` to listen on IPv6 and IPv4 | | http_port | number | MYMPD_HTTP_PORT | 80 | Port to listen for plain http requests. Redirects to `ssl_port` if `ssl` is set to `true`. *1 | diff --git a/docs/installation/compiling/cmake.md b/docs/installation/compiling/cmake.md index 26abd41a4..05f248522 100644 --- a/docs/installation/compiling/cmake.md +++ b/docs/installation/compiling/cmake.md @@ -30,6 +30,7 @@ sudo make -C build install | MYMPD_ENABLE_IPV6 | ON | Enables IPv6 | | MYMPD_ENABLE_ASAN | OFF | Enables build with address sanitizer | | MYMPD_ENABLE_LIBID3TAG | ON | Enables libid3tag support | +| MYMPD_ENABLE_MYGPIOD | ON | Enables myGPIOd support | | MYMPD_ENABLE_LUA | ON | Enables lua support | | MYMPD_ENABLE_TSAN | OFF | Enables build with thread san | | MYMPD_ENABLE_UBSAN | OFF | Enables build with undefined behavior sanitizer | diff --git a/docs/installation/compiling/index.md b/docs/installation/compiling/index.md index b61b01ba5..936429153 100644 --- a/docs/installation/compiling/index.md +++ b/docs/installation/compiling/index.md @@ -40,6 +40,7 @@ myMPD has only a few dependencies beside the standard c libraries. Not installin - libid3tag - to extract embedded coverimages - flac - to extract embedded coverimages - liblua >= 5.3.0 - for myMPD scripting + - libmygpio - for GPIO scripting functions You can type `./build.sh installdeps` as root to install the dependencies (works only for supported distributions). For all other distributions you must install the packages manually. diff --git a/docs/installation/docker.md b/docs/installation/docker.md index 374079d44..3904cea2b 100644 --- a/docs/installation/docker.md +++ b/docs/installation/docker.md @@ -28,33 +28,49 @@ Starts the myMPD docker container: - Disables SSL - Listen on port 8080 -Docker Compose: +### Volumes -``` ---- -version: "3.x" +| VOLUME | DESCRIPTION | +| ------ | ----------- | +| `/run/mpd/socket` | MPD listening socket. | +| `/run/mygpiod/socket` | Optional myGPIOd socket for GPIO scripting support. | +| `/docker/mympd/workdir` | myMPD working directory. Must exist and be writeable by uid 1000. | +| `/docker/mympd/cachedir` | myMPD cache directory. Must exist and be writeable by uid 1000. | +| `/var/lib/mpd/music/` | MPD music directory. Use the same path in the container to enable auto detection. | +| `/var/lib/mpd/playlists/` | MPD playlist directory. Use the same path in the container to enable auto detection. | + +### Docker Compose + +Save this as `docker-compose.yml`: + +```yml services: mympd: image: ghcr.io/jcorporation/mympd/mympd container_name: mympd - network_mode: "host" + ports: + - 8080:8080 user: 1000:1000 environment: - UMASK_SET=022 - MYMPD_SSL=false - MYMPD_HTTP_PORT=8080 volumes: - - /path/to/mpd/socket:/run/mpd/socket - - /path/to/mympd/docker/workdir:/var/lib/mympd/ - - /path/to/mympd/docker/cachedir:/var/cache/mympd/ - - /path/to/music/dir/:/music/:ro - - /path/to/playlists/dir/:/playlists/:ro + - /run/mpd/socket:/run/mpd/socket + ## Optional for myGPIOd support + ## - /run/mygpiod/socket:/run/mygpiod/socket + - /docker/mympd/workdir:/var/lib/mympd/ + - /docker/mympd/cachedir:/var/cache/mympd/ + - /var/lib/mpd/music/:/var/lib/mpd/music/:ro + - /var/lib/mpd/playlists/:/var/lib/mpd/playlists/:ro restart: unless-stopped ``` -Docker CLI: +Setup: `docker compose up -d` -``` +### Docker CLI + +```sh docker run -d \ --name=mympd \ --net="host" \ @@ -62,15 +78,23 @@ docker run -d \ -e UMASK_SET=022 \ -e MYMPD_SSL=false \ -e MYMPD_HTTP_PORT=8080 \ - -v /path/to/mpd/socket:/run/mpd/socket \ - -v /path/to/mympd/docker/workdir:/var/lib/mympd/ \ - -v /path/to/mympd/docker/cachedir:/var/cache/mympd/ \ - -v /path/to/music/dir/:/music/:ro \ - -v /path/to/playlists/dir/:/playlists/:ro \ + -v /run/mpd/socket:/run/mpd/socket \ + ## Optional for myGPIOd support + ## -v /run/mygpiod/socket:/run/mygpiod/socket \ + -v /docker/mympd/workdir:/var/lib/mympd/ \ + -v /docker/mympd/cachedir:/var/cache/mympd/ \ + -v /var/lib/mpd/music/:/var/lib/mpd/music/:ro \ + -v /var/lib/mpd/playlists/:/var/lib/mpd/playlists/:ro \ --restart unless-stopped \ ghcr.io/jcorporation/mympd/mympd ``` +### Logs + +```sh +docker logs -f mympd +``` + ## myMPD configuration You can configure some basic options of myMPD via startup options or environment variables. diff --git a/docs/references/filesystem-hierarchy.md b/docs/references/filesystem-hierarchy.md index 4d949fb1f..3b09719e3 100644 --- a/docs/references/filesystem-hierarchy.md +++ b/docs/references/filesystem-hierarchy.md @@ -10,6 +10,7 @@ myMPD uses GNU standard installation directories. | ---------- | ----------- | | /etc/init.d/mympd | myMPD startscript (sysVinit or open-rc) | | /lib/systemd/system/mympd.service | Systemd unit | +| /lib/systemd/user/mympd.service | Systemd user unit | | /usr/bin/mympd | myMPD executable | | /usr/bin/mympd-script | Executable to trigger and post myMPD scripts | | /var/cache/mympd/ | myMPD cache directory | @@ -20,7 +21,7 @@ myMPD uses GNU standard installation directories. | /var/lib/mympd/empty/ | Intentionally empty directory | | /var/lib/mympd/pics/ | Root folder for images | | /var/lib/mympd/pics/backgrounds/ | Backgroundimages | -| /var/lib/mympd/pics/thumbs/ | Folder for homeicon, webradio and stream images | +| /var/lib/mympd/pics/thumbs/ | Folder for homeicon, webradio, playlist and stream images | | /var/lib/mympd/pics/``/ | Images for e.g. AlbumArtist, Artist, Genre, ... | | /var/lib/mympd/scripts/ | Directory for lua scripts | | /var/lib/mympd/smartpls/ | Directory for smart playlists | diff --git a/docs/references/pictures.md b/docs/references/pictures.md index fb5402fcb..973d62ec7 100644 --- a/docs/references/pictures.md +++ b/docs/references/pictures.md @@ -71,6 +71,10 @@ Create a directory named `AlbumArtist` under `/var/lib/mympd/pics`. Add pictures Pictures for the home icons must be placed in the directory `/var/lib/mympd/pics/thumbs`. +### Playlist pictures + +Pictures for playlists must be placed in the directory `/var/lib/mympd/pics/thumbs`. + ### Background images Background images must be saved in the `/var/lib/mympd/pics/backgrounds` folder. @@ -86,8 +90,11 @@ myMPD recognizes following file extensions: You can add custom placeholder images for albumart. - `/var/lib/mympd/pics/thumbs/coverimage-booklet.webp` +- `/var/lib/mympd/pics/thumbs/coverimage-folder.webp` - `/var/lib/mympd/pics/thumbs/coverimage-mympd.webp` - `/var/lib/mympd/pics/thumbs/coverimage-notavailable.webp` +- `/var/lib/mympd/pics/thumbs/coverimage-playlist.webp` +- `/var/lib/mympd/pics/thumbs/coverimage-smartpls.webp` - `/var/lib/mympd/pics/thumbs/coverimage-stream.webp` You can use every supported file extension. diff --git a/docs/references/translating_status.md b/docs/references/translating_status.md index 7e71c03b5..cf361554f 100644 --- a/docs/references/translating_status.md +++ b/docs/references/translating_status.md @@ -1,14 +1,14 @@ -- bg-BG: 991 missing phrases -- es-AR: fully translated -- es-ES: 853 missing phrases -- es-VE: 853 missing phrases -- fi-FI: 850 missing phrases -- fr-FR: 1 missing phrases -- it-IT: fully translated -- ja-JP: 12 missing phrases +- bg-BG: 1008 missing phrases +- es-AR: 5 missing phrases +- es-ES: 872 missing phrases +- es-VE: 870 missing phrases +- fi-FI: 867 missing phrases +- fr-FR: 5 missing phrases +- it-IT: 5 missing phrases +- ja-JP: 5 missing phrases - ko-KR: fully translated -- nl-NL: 1 missing phrases -- pl-PL: fully translated -- ru-RU: 74 missing phrases -- zh-Hans: fully translated -- zh-Hant: fully translated +- nl-NL: 5 missing phrases +- pl-PL: 23 missing phrases +- ru-RU: 97 missing phrases +- zh-Hans: 23 missing phrases +- zh-Hant: 23 missing phrases diff --git a/docs/references/webserver-uris.md b/docs/references/webserver-uris.md index 2647b5947..6b771dcc1 100644 --- a/docs/references/webserver-uris.md +++ b/docs/references/webserver-uris.md @@ -18,8 +18,10 @@ Reference of all webserver uris. | `/serverinfo` | Returns the ip address of myMPD | | `/browse/` | Prints the list of [published directories]({{ site.baseurl }}/references/published-directories) | | `/ca.crt` | Returns the myMPD CA certificate | +| `/folderart?path=` | Returns the folderart thumbnail. | +| `/playlistart?playlist=` | Returns the playlistart thumbnail. | | `/proxy?uri=` | Fetches the response from the uri (GET), allowed hosts: `jcorporation.github.io`, `musicbrainz.org`, `listenbrainz.org` | | `/stream/` | Reverse proxy for mpd http stream | -| `/tagart?uri=/` | Returns the tagart | +| `/tagart?tag=&value=` | Returns the tagart thumbnail. | | `/ws/` | Websocket endpoint | {: .table .table-sm } diff --git a/docs/scripting/index.md b/docs/scripting/index.md index 5049a6aff..f6e6fb93b 100644 --- a/docs/scripting/index.md +++ b/docs/scripting/index.md @@ -21,9 +21,13 @@ myMPD provides custom lua functions through the `mympd` lua library. | FUNCTION | DESCRIPTION | | -------- | ----------- | -| `mympd.api` | Access to the myMPD API | -| `mympd.http_client` | Simple HTTP client | -| `mympd.init` | Initializes the [Lua table mympd_state]({{ site.baseurl }}/scripting/lua-table-mympd_state) | +| `mympd.api` | Access to the myMPD API. | +| `mympd.gpio_blink` | Connects to myGPIOd and blinks a GPIO with given timeout and interval. | +| `mympd.gpio_get` | Connects to myGPIOd and returns the active state of a GPIO. | +| `mympd.gpio_set` | Connects to myGPIOd and sets the active value of a GPIO. | +| `mympd.gpio_toggle` | Connects to myGPIOd and toggles the active value of a GPIO. | +| `mympd.http_client` | Simple HTTP client. | +| `mympd.init` | Initializes the [Lua table mympd_state]({{ site.baseurl }}/scripting/lua-table-mympd_state). | | `mympd.os_capture` | Executes a system command and capture its output. | {: .table .table-sm } @@ -40,7 +44,7 @@ rc, result = mympd.api("method", params) | PARAMETER | TYPE | DESCRIPTION | | --------- | ---- | ----------- | | method | string | myMPD API method | -| params | lua table | the jsonrpc parameters | +| params | lua table | the jsonrpc parameters | {: .table .table-sm } **Returns:** @@ -135,6 +139,31 @@ systemctl daemon-reload systemctl restart mympd ``` +### GPIO interface + +The GPIO interface requires a configured [myGPIOd](https://github.com/jcorporation/myGPIOd). + +All functions are connecting to the socket `/run/mygpiod/socket`, issues the command and disconnects. + +```lua +-- Set non default socket for myGPIOd connection +mympd.mygpiod_socket = "/run/mygpiod/socket.debug" + +-- Blink a GPIO at given timeout and interval +rc = mympd.gpio_blink(gpio, timeout_ms, interval_ms) + +-- Get the active state of a GPIO +-- 0 = inactive, 1 = active +state = mympd.gpio_get(gpio) + +-- Sets the active state of a GPIO +-- 0 = inactive, 1 = active +rc = mympd.gpio_set(gpio, state) + +-- Toggles the active state of a GPIO +rc = mympd.gpio_toggle(gpio) +``` + ## Lua manual - [Lua manual](https://www.lua.org/manual/5.4/) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..4b50c87ba --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,121 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import pluginJsdoc from "eslint-plugin-jsdoc"; + +export default [ + pluginJs.configs.recommended, + pluginJsdoc.configs['flat/recommended-typescript-flavor'], + { + files: [ + "htdocs/sw.js", + "release/htdocs/js/mympd.js" + ], + plugins: { + jsdoc: pluginJsdoc + }, + linterOptions: { + reportUnusedDisableDirectives: "off" + }, + languageOptions: { + sourceType: "script", + globals: { + ...globals.browser, + "Atomics": "readonly", + "SharedArrayBuffer": "readonly", + "BSN": true, + "i18n": true, + "MediaMetadata": true + } + }, + rules: { + "block-scoped-var": "error", + "camelcase": "error", + "default-case": "error", + "default-case-last": "error", + "eqeqeq": [ "error", "always", { + "null": "ignore" + }], + "semi": [ 2, "always"], + "no-alert": "error", + "no-caller": "error", + "no-console": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-implied-eval": "error", + "no-invalid-this": "error", + "no-restricted-globals": "error", + "no-restricted-properties": "error", + "no-return-assign": "error", + "no-self-compare": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-useless-concat": "error", + "no-var": "error", + "prefer-const": "error", + "jsdoc/check-syntax": 1, + "jsdoc/newline-after-description": "off", + "jsdoc/no-blank-block-descriptions": 1, + "jsdoc/no-defaults": 1, + "jsdoc/no-undefined-types": [ "warn", { + "definedTypes": [ + "ChildNode", + "ParentNode" + ] + }], + "jsdoc/require-returns": [ "error", { + "forceRequireReturn": true + }] + } + }, + { + files: [ + "release/htdocs/sw.min.js", + "release/htdocs/js/mympd.min.js", + "release/htdocs/js/i18n.min.js" + ], + linterOptions: { + reportUnusedDisableDirectives: "off" + }, + languageOptions: { + sourceType: "script", + globals: { + ...globals.browser, + "Atomics": "readonly", + "SharedArrayBuffer": "readonly", + "BSN": true, + "i18n": true, + "MediaMetadata": true + }, + }, + rules: { + "block-scoped-var": "error", + "camelcase": "error", + "default-case-last": "error", + "eqeqeq": [ "error", "always", { + "null": "ignore" + }], + "no-alert": "error", + "no-caller": "error", + "no-console": "off", + "no-empty": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-fallthrough": "off", + "no-implied-eval": "error", + "no-invalid-this": "error", + "no-redeclare": [ "error", { + "builtinGlobals": false + }], + "no-restricted-globals": "error", + "no-restricted-properties": "error", + "no-return-assign": "error", + "no-self-compare": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-unused-vars": "off", + "no-useless-concat": "off", + "no-var": "error", + "prefer-const": "error" + } + } +]; diff --git a/htdocs/assets/coverimage-folder.svg b/htdocs/assets/coverimage-folder.svg new file mode 100644 index 000000000..6212c32fa --- /dev/null +++ b/htdocs/assets/coverimage-folder.svg @@ -0,0 +1,2 @@ + + diff --git a/htdocs/assets/coverimage-playlist.svg b/htdocs/assets/coverimage-playlist.svg new file mode 100644 index 000000000..6974b6750 --- /dev/null +++ b/htdocs/assets/coverimage-playlist.svg @@ -0,0 +1,2 @@ + + diff --git a/htdocs/assets/coverimage-smartpls.svg b/htdocs/assets/coverimage-smartpls.svg new file mode 100644 index 000000000..88447c1bb --- /dev/null +++ b/htdocs/assets/coverimage-smartpls.svg @@ -0,0 +1,2 @@ + + diff --git a/htdocs/css/mympd.css b/htdocs/css/mympd.css index baca6f929..f2a46c1e5 100644 --- a/htdocs/css/mympd.css +++ b/htdocs/css/mympd.css @@ -21,11 +21,11 @@ body.not-mobile .card { margin-bottom: 0; } -body.not-mobile .album-cover-grid > div { +body.not-mobile .cover-grid > div { display: none; } -body.not-mobile .album-cover-grid:hover > div { +body.not-mobile .cover-grid:hover > div { display: block; } @@ -38,7 +38,7 @@ body.mobile > footer > nav { } :root { - --mympd-thumbnail-size: 175px; + --grid-thumbnail-size: 175px; --mympd-highlightcolor: var(--bs-green); --mympd-highlightcolor-contrast: var(--bs-light); --mympd-backgroundcolor: var(--bs-black); @@ -364,8 +364,8 @@ th, #volumeMenu::after, #localPlaybackMenu::after, - #footerPlaying, - #footerCover { + #footerSettingsPlayback > button:nth-child(2), + #footerInfo { display: none !important; } } @@ -409,6 +409,7 @@ th, width: 2rem; text-align: right; word-break: keep-all; + padding: 0.25rem !important; } table[data-rw="false"] [data-col="Action"] a[data-action="quickRemove"] { @@ -419,10 +420,6 @@ table.smallWidth [data-col="Action"] { word-break: break-all; } -th[data-col="Action"] { - padding: 0.25rem !important; -} - div.card { word-break: break-word; margin-bottom: 2rem; @@ -458,7 +455,7 @@ div#homeActions { border-radius: 0; } -.home-icons .card-body { +.home-icons .card-title { font-size: 2rem; height: 4rem; width: 4rem; @@ -471,7 +468,7 @@ div#homeActions { text-align: center; } -.home-icons .card-footer { +.home-icons .card-body { border-top: none; background-color: transparent; } @@ -485,56 +482,64 @@ div#homeActions { } .card-grid { - width: var(--mympd-thumbnail-size); + width: var(--grid-thumbnail-size); min-height: 0 !important; margin-bottom: 1.25rem; overflow: hidden; max-width: 100%; + background-color: var(--bs-card-cap-bg); } -.card-footer-grid { +.card-body-grid { white-space: var(--mympd-card-footer-word-wrap); font-size: 0.9rem; } -.card-footer-grid > * { +.card-body-grid > * { overflow: hidden; text-overflow: ellipsis; } -.card-footer-tags a { - margin-top: 1rem; - display: inline-block; - width: 50%; +.card-footer-grid a { + flex: 1 1 auto !important; + padding: 0.5rem 0; text-align: center; } -.card-footer-tags a:last-child { - text-align: center; +.card-footer-grid button { + padding: 0.5rem 0; } -.album-cover-grid { +.cover-grid { background-size: cover; background-position: center center; overflow: hidden; - width: var(--mympd-thumbnail-size); - height: var(--mympd-thumbnail-size); + width: var(--grid-thumbnail-size); + height: var(--grid-thumbnail-size); max-width: 100%; padding: 0.5rem; } +.thumbnail { + background-size: cover; + background-position: center center; + overflow: hidden; + width: 2rem; + height: 2rem; +} + @media only screen and (300px <= width <= 400px) { .card-grid { width: calc((100vw - 4rem) / 2); } - .album-cover-grid { + .cover-grid { width: calc((100vw - 4rem) / 2); height: calc((100vw - 4rem) / 2); } } -.album-cover-loading { +.cover-loading { background-image: url("../assets/coverimage-mympd"); } @@ -666,12 +671,9 @@ tfoot td { width: 60vw; } +.dragover, .dragover > td { - border-top: 2px solid var(--mympd-highlightcolor); -} - -.dragover-th { - border-left: 2px solid var(--mympd-highlightcolor); + border-top: 2px solid var(--mympd-highlightcolor) !important; } [draggable] { @@ -705,6 +707,10 @@ div.key { background-color: inherit; } +.offcanvas-body { + --bs-offcanvas-padding-x: 0; +} + .offcanvas-body .dropdown-item { padding: 0.2rem 1.5rem; } @@ -868,15 +874,15 @@ div#modalPictureImg { } div#BrowseFilesystemImages { - height: calc(var(--mympd-thumbnail-size, 175px) + 1rem); + height: calc(var(--grid-thumbnail-size, 175px) + 1rem); margin-bottom: 1rem; overflow: auto hidden; white-space: nowrap; } div#BrowseFilesystemImages > div { - height: var(--mympd-thumbnail-size, 175px); - width: var(--mympd-thumbnail-size, 175px); + height: var(--grid-thumbnail-size, 175px); + width: var(--grid-thumbnail-size, 175px); background-position: center center; background-size: cover; display: inline-block; @@ -895,7 +901,7 @@ div.homeIconPreview { padding-top: 1rem; } -div.album-grid-mouseover { +div.gridQuickButton { background-color: rgba(0, 0, 0, 50%); color: var(--bs-white); width: 3rem; @@ -904,7 +910,7 @@ div.album-grid-mouseover { font-size: 2.4rem; } -div.album-grid-mouseover:hover { +div.gridQuickButton:hover { background-color: var(--mympd-highlightcolor); } @@ -997,19 +1003,6 @@ button.select-inner-button { min-width: 6rem; } -.sort-dir::before { - font-size: 1.2rem; - line-height: 1; -} - -.sort-asc::before { - content: 'arrow_drop_down'; -} - -.sort-desc::before { - content: 'arrow_drop_up'; -} - div.alert-warning::before, div.alert-danger::before, div.alert-secondary::before, @@ -1113,6 +1106,8 @@ button.weekday { .dropdown-divider2 { height: 0; margin-top: 1.5rem; + margin-left: -1rem; + margin-right: -1rem; overflow: hidden; opacity: 1; } @@ -1123,21 +1118,20 @@ button.weekday { } table[data-mode="select"] > thead > tr > th[data-col="Action"] > a, -table[data-mode="select"] > tbody > tr > td[data-col="Action"] > a { +table[data-mode="select"] > tbody > tr > td[data-col="Action"] > a, +div[data-mode="select"] .card-footer > a { display: none; } :not(table[data-mode="select"]) > thead > tr > th[data-col="Action"] > button, -:not(table[data-mode="select"]) > tbody > tr > td[data-col="Action"] > button { +:not(table[data-mode="select"]) > tbody > tr > td[data-col="Action"] > button, +:not(div[data-mode="select"]) > div > div > .card-footer > button { display: none; } -div[data-mode="select"] .card-footer > button { - display: block !important; -} - -div[data-mode="select"] .card-body > div { - display: none !important; +th[data-col="Action"] > button, +td[data-col="Action"] > button { + padding: 0; } tr.selected > td, @@ -1257,3 +1251,24 @@ h4.offcanvas-title-stream::before { .playbackPopoverBtns input { max-width: 6rem; } + +.fieldsEnabled > li > div.fieldsAvailableBtns { + display: none; +} + +.fieldsAvailable > li > div.fieldsEnabledBtns { + display: none; +} + +.fieldsEnabled > li:last-child > div.fieldsEnabledBtns > button:last-child { + visibility: hidden; +} + +.fieldsEnabled > li:first-child > div.fieldsEnabledBtns > button:nth-child(2) { + visibility: hidden; +} + +table div.alert { + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); +} diff --git a/htdocs/index.html b/htdocs/index.html index f33924a19..dc7af418e 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -113,11 +113,13 @@ -
+
+ +
@@ -145,18 +147,13 @@
- - +
-
+
- +

@@ -169,7 +166,7 @@

- +
@@ -233,11 +230,19 @@
-
- +
+ +
- +
+
+ +
+
+ +
-
- - - - - - -
-
+
@@ -286,17 +289,14 @@ - + - +
-
- -
- +
+
+ +
+
+ +
-
- - - - - -
-
+
@@ -345,20 +344,17 @@ - + - +
-
- -
- +
+
+ +
+
+ +
-
-
- - - - - -
-
+
+
@@ -407,20 +402,17 @@ - + - +
-
- -
- +
+
+ +
+
+ +
-
-
- - - - - -
-
+
+
@@ -475,11 +466,8 @@ -
- -
- +
+
+ +
+
+ +
-
- - - - - - - - - - -
-
+
@@ -524,9 +506,9 @@ - + - + @@ -585,7 +567,7 @@
- +
+
+ +
-
-
- - -
- - - - - - - -
+
+ +
+

+
@@ -633,14 +610,15 @@
+
+ +
-
-
-
+
@@ -663,9 +641,9 @@ - + - + @@ -680,19 +658,8 @@ -
- - -
-
- -
- +
+
+ +
+
+ +
-
-
-
+
@@ -732,7 +703,7 @@
- +
- - +
-
+
- +
- - - - - - -
+
+ + + + + + +
+
@@ -800,7 +768,7 @@
- +
+
+ +
-
- - - - - - -
-
+
@@ -850,10 +813,8 @@ search -
- -
- +
+
+
+ +
+
+ +
-
-
-
+
@@ -904,11 +869,19 @@ -
- +
+ +
- +
+
+ +
+
+ +
-
- - - - - - -
-
+
@@ -955,11 +926,8 @@ -
- -
- +
+
+ +
+
+ +
-
- - - - - - -
-
+
@@ -1023,7 +989,18 @@
- + + +
+
+
+
+ +
-
- - - - - - -
-
+
@@ -1075,15 +1047,15 @@
-