diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 000000000..0a04ad6cd --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,26 @@ +task: + name: freebsd-build + freebsd_instance: + image_family: freebsd-12-2 + + prepare_script: + - pkg install -yq git cmake jpeg-turbo mysql80-client ffmpeg libvncserver libjwt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap + + configure_script: + - git submodule update --init --recursive + - mkdir build + - cd build + - cmake --version + - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 + + build_script: + - cd build + - make -j3 + + install_script: + - cd build + - make install + + test_script: + - cd build/tests + - ./tests "~[notCI]" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..8785e55b2 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Reindent CMakeLists +6c9983155c65848a3e67976445cd20fb4fbfe108 \ No newline at end of file diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000..603000225 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,2 @@ +paths-ignore: + - dep/ diff --git a/.github/workflows/ci-xenial.yml b/.github/workflows/ci-xenial.yml new file mode 100644 index 000000000..4ea605267 --- /dev/null +++ b/.github/workflows/ci-xenial.yml @@ -0,0 +1,25 @@ +name: CI Xenial + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-16.04 + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install libavdevice-dev libcurl4-gnutls-dev libvlc-dev libvncserver-dev libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libpolkit-gobject-1-dev + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..8b071a662 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,79 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 3 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp', 'javascript'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Clean install dependencies and build + run: | + git submodule init + git submodule update --init --recursive + sudo apt-get update + sudo apt-get install libavdevice-dev libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libjwt-gnutls-dev + sudo apt-get install libbz2-dev libgcrypt20-dev libcurl4-gnutls-dev libjpeg-turbo8-dev libturbojpeg0-dev + sudo apt-get install default-libmysqlclient-dev libpcre3-dev libpolkit-gobject-1-dev libv4l-dev libvlc-dev + sudo apt-get install libdate-manip-perl libdbd-mysql-perl libphp-serialization-perl libsys-mmap-perl + sudo apt-get install libwww-perl libdata-uuid-perl libssl-dev libcrypt-eksblowfish-perl libdata-entropy-perl + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl- + + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index 152802d29..e617d937c 100644 --- a/.gitignore +++ b/.gitignore @@ -120,15 +120,10 @@ src/CMakeFiles/ src/cmake_install.cmake src/libzm.a src/nph-zms -src/zm_config_data.h -src/zm_config_defines.h -src/zma src/zmc src/zmf src/zms src/zmu -src/zoneminder-zma.8 -src/zoneminder-zma.8.gz src/zoneminder-zmc.8 src/zoneminder-zmc.8.gz src/zoneminder-zmf.8 @@ -157,4 +152,6 @@ web/undef.log zm.conf zmconfgen.pl zmlinkcontent.sh +zm_config_data.h +zm_config_defines.h **/.DS_Store diff --git a/.gitmodules b/.gitmodules index eb0e282a2..fd07ba226 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,10 @@ [submodule "web/api/app/Plugin/Crud"] path = web/api/app/Plugin/Crud - url = https://github.com/ZoneMinder/crud.git + url = https://github.com/FriendsOfCake/crud.git branch = 3.0 [submodule "web/api/app/Plugin/CakePHP-Enum-Behavior"] path = web/api/app/Plugin/CakePHP-Enum-Behavior - url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git + Url = https://github.com/ZoneMinder/CakePHP-Enum-Behavior.git +[submodule "dep/RtspServer"] + path = dep/RtspServer + url = https://github.com/ZoneMinder/RtspServer diff --git a/.travis.yml b/.travis.yml index da05d8970..c370e9318 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,16 +33,7 @@ install: env: - SMPFLAGS=-j4 OS=eslint DIST=eslint - - SMPFLAGS=-j4 OS=el DIST=7 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=el DIST=8 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=fedora DIST=31 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=fedora DIST=32 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=fedora DIST=33 DOCKER_REPO=knnniggett/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=ubuntu DIST=focal DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack - - SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack compiler: - gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index 98457b7c3..700c80a20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,287 +2,266 @@ # Created by mastertheknife (Kfir Itzhak) # For more information and installation, see the INSTALL file # -cmake_minimum_required (VERSION 2.8.7) -project (zoneminder) -file (STRINGS "version" zoneminder_VERSION) +cmake_minimum_required(VERSION 3.5.0) +project(zoneminder) +file(STRINGS "version" zoneminder_VERSION) # make API version a minor of ZM version set(zoneminder_API_VERSION "${zoneminder_VERSION}.1") # Make sure the submodules are there -if( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" ) -message( SEND_ERROR "The git submodules are not available. Please run -git submodule update --init --recursive") -endif( NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php" ) +if(NOT EXISTS "${CMAKE_SOURCE_DIR}/web/api/app/Plugin/Crud/Lib/CrudControllerTrait.php") + message(SEND_ERROR "The git submodules are not available. Please run git submodule update --init --recursive") +endif() # CMake does not allow out-of-source build if CMakeCache.exists # in the source folder. Abort and notify the user -if( - (NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) - message(FATAL_ERROR " You are attempting to do an out-of-source build, - but a cmake cache file for an in-source build exists. Please delete - the file CMakeCache.txt from the source folder to proceed.") -endif( - (NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) +if((NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) AND (EXISTS "${CMAKE_SOURCE_DIR}/CMakeCache.txt")) + message(FATAL_ERROR " You are attempting to do an out-of-source build, + but a cmake cache file for an in-source build exists. Please delete + the file CMakeCache.txt from the source folder to proceed.") +endif() # Default build type. To change the build type, # use the CMAKE_BUILD_TYPE configuration option. if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE - Release CACHE STRING "Build type: Release or Debug" FORCE) -endif(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Release or Debug" FORCE) +endif() # Can assist in troubleshooting #set(CMAKE_VERBOSE_MAKEFILE ON) #set(CMAKE_INSTALL_ALWAYS ON) -# Host OS Check -set(HOST_OS "") -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - set(HOST_OS "linux") -endif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") -if(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") - set(HOST_OS "solaris") - set(SOLARIS 1) -endif(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") -if(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") - set(HOST_OS "BSD") - set(BSD 1) -endif(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") -if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(HOST_OS "darwin") -endif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") -if(NOT HOST_OS) - message(FATAL_ERROR - "ZoneMinder was unable to deterimine the host OS. Please report this. Value of CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") -endif(NOT HOST_OS) - -set (CMAKE_CXX_STANDARD 11) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # Default CLFAGS and CXXFLAGS: -set(CMAKE_C_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") -set(CMAKE_CXX_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2") -set(CMAKE_C_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") -set(CMAKE_CXX_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") -set(CMAKE_C_FLAGS_OPTIMISED "-Wall -D__STDC_CONSTANT_MACROS -O3") -set(CMAKE_CXX_FLAGS_OPTIMISED "-Wall -D__STDC_CONSTANT_MACROS -O3") +set(CMAKE_C_FLAGS_RELEASE "-O2") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_CXX_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_OPTIMISED "-O3") +set(CMAKE_CXX_FLAGS_OPTIMISED "-O3") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +include(ConfigureBaseTargets) +include(CheckPlatform) + # GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. # Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, # because passing -fmpu=neon is unsafe to processors that don't support neon # Arm neon support only tested on Linux. If your arm hardware is running a non-Linux distro and is using gcc then contact us. -IF (CMAKE_SYSTEM_NAME MATCHES "Linux") - string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ZM_SYSTEM_PROC) - IF((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) - execute_process(COMMAND uname -m OUTPUT_VARIABLE ZM_SYSTEM_PROC ERROR_VARIABLE ZM_SYSTEM_PROC_ERR) - - # maybe make the following error checks fatal - IF(ZM_SYSTEM_PROC_ERR) - message(WARNING "\nAn error occurred while attempting to determine the system processor:\n${ZM_SYSTEM_PROC_ERR}") - ENDIF(ZM_SYSTEM_PROC_ERR) - IF(NOT ZM_SYSTEM_PROC) - message(WARNING "\nUnable to determine the system processor. This may cause a build failure.\n") - ENDIF(ZM_SYSTEM_PROC) - - ENDIF((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) - - IF(ZM_SYSTEM_PROC MATCHES "^arm") - IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - EXEC_PROGRAM(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) - IF(neonresult EQUAL 0) - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") - ELSE(neonresult EQUAL 0) - add_definitions(-DZM_STRIP_NEON=1) - message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") - ENDIF(neonresult EQUAL 0) - ENDIF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - ENDIF(ZM_SYSTEM_PROC MATCHES "^arm") -ENDIF (CMAKE_SYSTEM_NAME MATCHES "Linux") +if(ZM_SYSTEM_PROC MATCHES "^arm") + if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + exec_program(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) + if(neonresult EQUAL 0) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") + else() + add_definitions(-DZM_STRIP_NEON=1) + message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") + endif() + endif() +endif() # Modules that we need: -include (GNUInstallDirs) -include (CheckIncludeFile) -include (CheckIncludeFiles) -include (CheckFunctionExists) -include (CheckPrototypeDefinition_fixed) -include (CheckTypeSize) -include (CheckStructHasMember) -include (CheckSendfile) +include(GNUInstallDirs) +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckFunctionExists) +include(CheckPrototypeDefinition_fixed) +include(CheckTypeSize) +include(CheckStructHasMember) +include(CheckSendfile) # Configuration options -mark_as_advanced( - FORCE ZM_EXTRA_LIBS - ZM_MYSQL_ENGINE - ZM_NO_MMAP - CMAKE_INSTALL_FULL_BINDIR - ZM_PERL_MM_PARMS - ZM_PERL_SEARCH_PATH - ZM_TARGET_DISTRO - ZM_PATH_MAP - ZM_PATH_ARP - ZM_CONFIG_DIR - ZM_CONFIG_SUBDIR - ZM_SYSTEMD - ZM_MANPAGE_DEST_PREFIX) +mark_as_advanced( + FORCE ZM_EXTRA_LIBS + ZM_MYSQL_ENGINE + ZM_NO_MMAP + CMAKE_INSTALL_FULL_BINDIR + ZM_PERL_MM_PARMS + ZM_PERL_SEARCH_PATH + ZM_TARGET_DISTRO + ZM_PATH_MAP + ZM_PATH_ARP + ZM_CONFIG_DIR + ZM_CONFIG_SUBDIR + ZM_SYSTEMD + ZM_MANPAGE_DEST_PREFIX) -set(ZM_RUNDIR "/var/run/zm" CACHE PATH - "Location of transient process files, default: /var/run/zm") -set(ZM_SOCKDIR "/var/run/zm" CACHE PATH - "Location of Unix domain socket files, default /var/run/zm") -set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH - "Location of temporary files, default: /tmp/zm") -set(ZM_LOGDIR "/var/log/zm" CACHE PATH - "Location of generated log files, default: /var/log/zm") -set(ZM_WEBDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/www" CACHE PATH - "Location of the web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/www") -set(ZM_CGIDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH - "Location of the cgi-bin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") -set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH - "Location of the web server cache busting files, default: /var/cache/zoneminder") -set(ZM_CONTENTDIR "/var/lib/zoneminder" CACHE PATH - "Location of dynamic content (events and images), default: /var/lib/zoneminder") -set(ZM_DB_HOST "localhost" CACHE STRING - "Hostname where ZoneMinder database located, default: localhost") -set(ZM_DB_NAME "zm" CACHE STRING - "Name of ZoneMinder database, default: zm") -set(ZM_DB_USER "zmuser" CACHE STRING - "Name of ZoneMinder database user, default: zmuser") -set(ZM_DB_PASS "zmpass" CACHE STRING - "Password of ZoneMinder database user, default: zmpass") -set(ZM_WEB_USER "" CACHE STRING - "The user apache or the local web server runs on. Leave empty for automatic detection. +option(ENABLE_WERROR "Fail the build if a compiler warning is emitted" 0) +option(BUILD_TEST_SUITE "Build the test suite" 0) +option(BUILD_MAN "Build man pages" 1) +option(ASAN "DEBUGGING: Build with AddressSanitizer (ASan) support" 0) +option(TSAN "DEBUGGING: Build with ThreadSanitizer (TSan) support" 0) + +if(ASAN AND TSAN) + message(FATAL_ERROR "ASAN and TSAN options are mutually exclusive") +endif() + +set(ZM_RUNDIR "/var/run/zm" CACHE PATH + "Location of transient process files, default: /var/run/zm") +set(ZM_SOCKDIR "/var/run/zm" CACHE PATH + "Location of Unix domain socket files, default /var/run/zm") +set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH + "Location of temporary files, default: /tmp/zm") +set(ZM_LOGDIR "/var/log/zm" CACHE PATH + "Location of generated log files, default: /var/log/zm") +set(ZM_WEBDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/www" CACHE PATH + "Location of the web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/www") +set(ZM_CGIDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH + "Location of the cgi-bin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") +set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH + "Location of the web server cache busting files, default: /var/cache/zoneminder") +set(ZM_CONTENTDIR "/var/lib/zoneminder" CACHE PATH + "Location of dynamic content (events and images), default: /var/lib/zoneminder") +set(ZM_FONTDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/fonts" CACHE PATH + "Location of the font files used for timestamping, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/fonts") + +set(ZM_DB_HOST "localhost" CACHE STRING + "Hostname where ZoneMinder database located, default: localhost") +set(ZM_DB_NAME "zm" CACHE STRING + "Name of ZoneMinder database, default: zm") +set(ZM_DB_USER "zmuser" CACHE STRING + "Name of ZoneMinder database user, default: zmuser") +set(ZM_DB_PASS "zmpass" CACHE STRING + "Password of ZoneMinder database user, default: zmpass") +set(ZM_WEB_USER "" CACHE STRING + "The user apache or the local web server runs on. Leave empty for automatic detection. If that fails, you can use this variable to force") -set(ZM_WEB_GROUP "" CACHE STRING - "The group apache or the local web server runs on, +set(ZM_WEB_GROUP "" CACHE STRING + "The group apache or the local web server runs on, Leave empty to be the same as the web user") -set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH - "Location where events are recorded to, default: ZM_CONTENTDIR/events") +set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH + "Location where events are recorded to, default: ZM_CONTENTDIR/events") set(ZM_DIR_SOUNDS "sounds" CACHE PATH - "Location to look for optional sound files, default: sounds") + "Location to look for optional sound files, default: sounds") set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH - "Web url to zms streaming server, default: /cgi-bin/nph-zms") + "Web url to zms streaming server, default: /cgi-bin/nph-zms") set(ZM_PATH_SHUTDOWN "/sbin/shutdown" CACHE PATH - "Path to shutdown binary, default: /sbin/shutdown") + "Path to shutdown binary, default: /sbin/shutdown") # Advanced set(ZM_PATH_MAP "/dev/shm" CACHE PATH - "Location to save mapped memory files, default: /dev/shm") + "Location to save mapped memory files, default: /dev/shm") set(ZM_PATH_ARP "" CACHE PATH - "Full path to compatible arp binary. Leave empty for automatic detection.") -set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH - "Location of ZoneMinder configuration, default system config directory") -set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH - "Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d") -set(ZM_EXTRA_LIBS "" CACHE STRING - "A list of optional libraries, separated by semicolons, e.g. ssl;theora") -set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING - "MySQL engine to use with database, default: InnoDB") -set(ZM_NO_MMAP "OFF" CACHE BOOL - "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you + "Full path to compatible arp binary. Leave empty for automatic detection.") +set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH + "Location of ZoneMinder configuration, default system config directory") +set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH + "Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d") +set(ZM_EXTRA_LIBS "" CACHE STRING + "A list of optional libraries, separated by semicolons, e.g. ssl;theora") +set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING + "MySQL engine to use with database, default: InnoDB") +set(ZM_NO_MMAP "OFF" CACHE BOOL + "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF") -set(ZM_NO_LIBVLC "OFF" CACHE BOOL -"Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") -set(ZM_NO_LIBVNC "OFF" CACHE BOOL -"Set to ON to skip libvnc checks and force building ZM without libvnc. default: OFF") -set(ZM_NO_CURL "OFF" CACHE BOOL - "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF") -set(ZM_NO_X10 "OFF" CACHE BOOL - "Set to ON to build ZoneMinder without X10 support. default: OFF") -set(ZM_ONVIF "ON" CACHE BOOL - "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not +set(ZM_NO_LIBVLC "OFF" CACHE BOOL + "Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") +set(ZM_NO_LIBVNC "OFF" CACHE BOOL + "Set to ON to skip libvnc checks and force building ZM without libvnc. default: OFF") +set(ZM_NO_CURL "OFF" CACHE BOOL + "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF") +set(ZM_NO_X10 "OFF" CACHE BOOL + "Set to ON to build ZoneMinder without X10 support. default: OFF") +set(ZM_ONVIF "ON" CACHE BOOL + "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not work with all cameras claiming to be ONVIF compliant. default: ON") -set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING - "By default, ZoneMinder's Perl modules are installed into the Vendor folders, +set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL + "Set to ON to skip building ZM with rtsp server support. default: OFF") +set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING + "By default, ZoneMinder's Perl modules are installed into the Vendor folders, as defined by your installation of Perl. You can change that here. Consult Perl's MakeMaker documentation for a definition of acceptable parameters. If you set this to something that causes the modules to be installed outside Perl's normal search path, then you will also need to set ZM_PERL_SEARCH_PATH accordingly.") -set(ZM_PERL_SEARCH_PATH "" CACHE PATH - "Use to add a folder to your Perl's search path. This will need to be set in cases +set(ZM_PERL_SEARCH_PATH "" CACHE PATH + "Use to add a folder to your Perl's search path. This will need to be set in cases where ZM_PERL_MM_PARMS has been modified such that ZoneMinder's Perl modules are installed outside Perl's default search path.") -set(ZM_TARGET_DISTRO "" CACHE STRING - "Build ZoneMinder for a specific distribution. Currently, valid names are: fc27, fc26, el7, OS13, FreeBSD") -set(ZM_SYSTEMD "OFF" CACHE BOOL - "Set to ON to force building ZM with systemd support. default: OFF") -set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH - "Relative path used to install ZoneMinder's Man pages into a - non-standard folder. Most Linux users will not need to change this. - BSD users may need to set this.") +set(ZM_TARGET_DISTRO "" CACHE STRING + "Build ZoneMinder for a specific distribution. Currently, valid names are: fc27, fc26, el7, OS13, FreeBSD") +set(ZM_SYSTEMD "OFF" CACHE BOOL + "Set to ON to force building ZM with systemd support. default: OFF") +set(ZM_MANPAGE_DEST_PREFIX "share/man" CACHE PATH + "Relative path used to install ZoneMinder's Man pages into a + non-standard folder. Most Linux users will not need to change this. + BSD users may need to set this.") # Reassign some variables if a target distro has been specified if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) - set(ZM_RUNDIR "/var/run/zoneminder") - set(ZM_SOCKDIR "/var/lib/zoneminder/sock") - set(ZM_TMPDIR "/var/lib/zoneminder/temp") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_CONFIG_DIR "/etc/zm") - set(ZM_CONFIG_SUBDIR "/etc/zm/conf.d") - set(ZM_WEBDIR "/usr/share/zoneminder/www") - set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") - set(ZM_DIR_EVENTS "/var/lib/zoneminder/events") - set(ZM_PATH_ZMS "/cgi-bin-zm/nph-zms") + set(ZM_RUNDIR "/var/run/zoneminder") + set(ZM_SOCKDIR "/var/lib/zoneminder/sock") + set(ZM_TMPDIR "/var/lib/zoneminder/temp") + set(ZM_LOGDIR "/var/log/zoneminder") + set(ZM_CONFIG_DIR "/etc/zm") + set(ZM_CONFIG_SUBDIR "/etc/zm/conf.d") + set(ZM_WEBDIR "/usr/share/zoneminder/www") + set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") + set(ZM_DIR_EVENTS "/var/lib/zoneminder/events") + set(ZM_PATH_ZMS "/cgi-bin-zm/nph-zms") elseif(ZM_TARGET_DISTRO STREQUAL "OS13") - set(ZM_RUNDIR "/var/run/zoneminder") - set(ZM_TMPDIR "/var/run/zoneminder") - set(ZM_CONTENTDIR "/var/run/zoneminder") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_WEB_USER "wwwrun") - set(ZM_WEB_GROUP "www") - set(ZM_WEBDIR "/srv/www/htdocs/zoneminder") - set(ZM_CGIDIR "/srv/www/cgi-bin") + set(ZM_RUNDIR "/var/run/zoneminder") + set(ZM_TMPDIR "/var/run/zoneminder") + set(ZM_CONTENTDIR "/var/run/zoneminder") + set(ZM_LOGDIR "/var/log/zoneminder") + set(ZM_WEB_USER "wwwrun") + set(ZM_WEB_GROUP "www") + set(ZM_WEBDIR "/srv/www/htdocs/zoneminder") + set(ZM_CGIDIR "/srv/www/cgi-bin") elseif(ZM_TARGET_DISTRO STREQUAL "FreeBSD") - set(ZM_RUNDIR "/var/run/zm") - set(ZM_SOCKDIR "/var/run/zm") - set(ZM_TMPDIR "/var/tmp/zm") - set(ZM_CONTENTDIR "/usr/local/var/lib/zoneminder") - set(ZM_WEB_USER "www") - set(ZM_WEB_GROUP "www") - set(ZM_CONFIG_DIR "/usr/local/etc/zm") - set(ZM_CONFIG_SUBDIR "/usr/local/etc/zm/conf.d") - set(ZM_WEBDIR "/usr/local/share/zoneminder/www") - set(ZM_CGIDIR "/usr/local/libexec/zoneminder/cgi-bin") - set(ZM_PERL_MM_PARMS "INSTALLDIRS=site") -endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) + set(ZM_RUNDIR "/var/run/zm") + set(ZM_SOCKDIR "/var/run/zm") + set(ZM_TMPDIR "/var/tmp/zm") + set(ZM_CONTENTDIR "/usr/local/var/lib/zoneminder") + set(ZM_WEB_USER "www") + set(ZM_WEB_GROUP "www") + set(ZM_CONFIG_DIR "/usr/local/etc/zm") + set(ZM_CONFIG_SUBDIR "/usr/local/etc/zm/conf.d") + set(ZM_WEBDIR "/usr/local/share/zoneminder/www") + set(ZM_CGIDIR "/usr/local/libexec/zoneminder/cgi-bin") + set(ZM_PERL_MM_PARMS "INSTALLDIRS=site") +endif() + +if(BUILD_MAN) + message(STATUS "Building man pages: Yes (default)") + set(ZM_PERL_MM_PARMS_FULL ${ZM_PERL_MM_PARMS}) +else() + message(STATUS "Building man pages: No") + list(APPEND ZM_PERL_MM_PARMS_FULL ${ZM_PERL_MM_PARMS} + "INSTALLMAN1DIR=none" + "INSTALLMAN3DIR=none") +endif() # Required for certain checks to work -set(CMAKE_EXTRA_INCLUDE_FILES - ${CMAKE_EXTRA_INCLUDE_FILES} stdio.h stdlib.h math.h signal.h - ) -# Required for including headers from the this folder -include_directories("${CMAKE_BINARY_DIR}") +set(CMAKE_EXTRA_INCLUDE_FILES ${CMAKE_EXTRA_INCLUDE_FILES} stdio.h stdlib.h math.h signal.h) # This is required to enable searching in lib64 (if exists), do not change set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) # Set the systemd flag if systemd is autodetected or ZM_SYSTEMD has been set if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) - set(WITH_SYSTEMD 1) -endif(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + set(WITH_SYSTEMD 1) +endif() # System checks check_include_file("libv4l1-videodev.h" HAVE_LIBV4L1_VIDEODEV_H) if(NOT HAVE_LIBV4L1_VIDEODEV_H) - check_include_file("linux/videodev.h" HAVE_LINUX_VIDEODEV_H) -endif(NOT HAVE_LIBV4L1_VIDEODEV_H) + check_include_file("linux/videodev.h" HAVE_LINUX_VIDEODEV_H) +endif() check_include_file("linux/videodev2.h" HAVE_LINUX_VIDEODEV2_H) check_include_file("execinfo.h" HAVE_EXECINFO_H) -if (HAVE_EXECINFO_H) +if(HAVE_EXECINFO_H) check_function_exists("backtrace" HAVE_DECL_BACKTRACE) - if (NOT HAVE_DECL_BACKTRACE) - find_library (EXECINFO_LIBRARY NAMES execinfo) - if (EXECINFO_LIBRARY) - list(APPEND ZM_BIN_LIBS "-lexecinfo") - endif (EXECINFO_LIBRARY) - endif (NOT HAVE_DECL_BACKTRACE) + if(NOT HAVE_DECL_BACKTRACE) + find_library(EXECINFO_LIBRARY NAMES execinfo) + if(EXECINFO_LIBRARY) + list(APPEND ZM_BIN_LIBS "-lexecinfo") + endif() + endif() check_function_exists("backtrace_symbols" HAVE_DECL_BACKTRACE_SYMBOLS) -endif (HAVE_EXECINFO_H) +endif() check_include_file("ucontext.h" HAVE_UCONTEXT_H) check_include_file("sys/sendfile.h" HAVE_SYS_SENDFILE_H) check_include_file("sys/syscall.h" HAVE_SYS_SYSCALL_H) @@ -294,326 +273,269 @@ check_type_size("ucontext_t" HAVE_UCONTEXT_T) # *** LIBRARY CHECKS *** -if (UNIX) - include (CheckLibraryExists) - CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) - if(NOT HAVE_CLOCK_GETTIME) - message(FATAL_ERROR "clock_gettime not found") - else(NOT HAVE_CLOCK_GETTIME) - list(APPEND ZM_BIN_LIBS "-lrt") - endif(NOT HAVE_CLOCK_GETTIME) -endif(UNIX) +if(UNIX) + include(CheckLibraryExists) + CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME) + if(NOT HAVE_CLOCK_GETTIME) + message(FATAL_ERROR "clock_gettime not found") + else() + list(APPEND ZM_BIN_LIBS "-lrt") + endif() +endif() # zlib find_package(ZLIB) if(ZLIB_FOUND) - set(HAVE_LIBZLIB 1) - list(APPEND ZM_BIN_LIBS "${ZLIB_LIBRARIES}") - include_directories("${ZLIB_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIR}") - check_include_file("zlib.h" HAVE_ZLIB_H) - set(optlibsfound "${optlibsfound} zlib") -else(ZLIB_FOUND) - set(optlibsnotfound "${optlibsnotfound} zlib") -endif(ZLIB_FOUND) + set(HAVE_LIBZLIB 1) + list(APPEND ZM_BIN_LIBS "${ZLIB_LIBRARIES}") + include_directories("${ZLIB_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIR}") + check_include_file("zlib.h" HAVE_ZLIB_H) + set(optlibsfound "${optlibsfound} zlib") +else() + set(optlibsnotfound "${optlibsnotfound} zlib") +endif() # Do not check for cURL if ZM_NO_CURL is on if(NOT ZM_NO_CURL) - # cURL - find_package(CURL) - if(CURL_FOUND) - set(HAVE_LIBCURL 1) - #list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) - include_directories(${CURL_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) - check_include_file("curl/curl.h" HAVE_CURL_CURL_H) - set(optlibsfound "${optlibsfound} cURL") - else(CURL_FOUND) - set(optlibsnotfound "${optlibsnotfound} cURL") - endif(CURL_FOUND) -endif(NOT ZM_NO_CURL) + # cURL + find_package(CURL) + if(CURL_FOUND) + set(HAVE_LIBCURL 1) + #list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) + include_directories(${CURL_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) + check_include_file("curl/curl.h" HAVE_CURL_CURL_H) + set(optlibsfound "${optlibsfound} cURL") + else() + set(optlibsnotfound "${optlibsnotfound} cURL") + endif() +endif() # jpeg find_package(JPEG) if(JPEG_FOUND) - set(HAVE_LIBJPEG 1) - list(APPEND ZM_BIN_LIBS "${JPEG_LIBRARIES}") - #link_directories(${JPEG_LIBRARY}) - include_directories("${JPEG_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}") - check_include_files("stdio.h;jpeglib.h" HAVE_JPEGLIB_H) - if(NOT HAVE_JPEGLIB_H) - message(FATAL_ERROR - "ZoneMinder requires libjpeg headers - check that libjpeg development packages are installed") - endif(NOT HAVE_JPEGLIB_H) -else(JPEG_FOUND) - message(FATAL_ERROR - "ZoneMinder requires jpeg but it was not found on your system") -endif(JPEG_FOUND) + set(HAVE_LIBJPEG 1) + list(APPEND ZM_BIN_LIBS "${JPEG_LIBRARIES}") + #link_directories(${JPEG_LIBRARY}) + include_directories("${JPEG_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${JPEG_INCLUDE_DIR}") + check_include_files("stdio.h;jpeglib.h" HAVE_JPEGLIB_H) + if(NOT HAVE_JPEGLIB_H) + message(FATAL_ERROR + "ZoneMinder requires libjpeg headers - check that libjpeg development packages are installed") + endif() +else() + message(FATAL_ERROR + "ZoneMinder requires jpeg but it was not found on your system") +endif() # LIBJWT find_package(LibJWT) if(LIBJWT_FOUND) - set(HAVE_LIBJWT 1) - set(optlibsfound "${optlibsfound} LIBJWT") - list(APPEND ZM_BIN_LIBS "${LIBJWT_LIBRARY}") -else(LIBJWT_FOUND) - set(optlibsnotfound "${optlibsnotfound} LIBJWT") -endif(LIBJWT_FOUND) + set(HAVE_LIBJWT 1) + set(optlibsfound "${optlibsfound} LIBJWT") + list(APPEND ZM_BIN_LIBS "${LIBJWT_LIBRARY}") +else() + set(optlibsnotfound "${optlibsnotfound} LIBJWT") +endif() # gnutls (using find_library and find_path) if(HAVE_LIBJWT) - find_library(GNUTLS_LIBRARIES gnutls) - if(GNUTLS_LIBRARIES) - set(HAVE_LIBGNUTLS 1) - list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") - find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) - if(GNUTLS_INCLUDE_DIR) - include_directories("${GNUTLS_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - endif(GNUTLS_INCLUDE_DIR) - mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) - check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) - set(optlibsfound "${optlibsfound} GnuTLS") - else(GNUTLS_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} GnuTLS") - endif(GNUTLS_LIBRARIES) -endif(HAVE_LIBJWT) + find_library(GNUTLS_LIBRARIES gnutls) + if(GNUTLS_LIBRARIES) + set(HAVE_LIBGNUTLS 1) + list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") + find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) + if(GNUTLS_INCLUDE_DIR) + include_directories("${GNUTLS_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) + check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) + set(optlibsfound "${optlibsfound} GnuTLS") + else() + set(optlibsnotfound "${optlibsnotfound} GnuTLS") + endif() +endif() # OpenSSL if(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT) - find_package(OpenSSL) - if(OPENSSL_FOUND) - set(HAVE_LIBOPENSSL 1) - set(HAVE_LIBCRYPTO 1) - list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") - include_directories("${OPENSSL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) - set(optlibsfound "${optlibsfound} OpenSSL") - else(OPENSSL_FOUND) - set(optlibsnotfound "${optlibsnotfound} OpenSSL") - endif(OPENSSL_FOUND) -endif(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT) + find_package(OpenSSL) + if(OPENSSL_FOUND) + set(HAVE_LIBOPENSSL 1) + set(HAVE_LIBCRYPTO 1) + list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") + include_directories("${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) + set(optlibsfound "${optlibsfound} OpenSSL") + else() + set(optlibsnotfound "${optlibsnotfound} OpenSSL") + endif() +endif() # pthread (using find_library and find_path) find_library(PTHREAD_LIBRARIES pthread) if(PTHREAD_LIBRARIES) - set(HAVE_LIBPTHREAD 1) - list(APPEND ZM_BIN_LIBS "${PTHREAD_LIBRARIES}") - find_path(PTHREAD_INCLUDE_DIR pthread.h) - if(PTHREAD_INCLUDE_DIR) - include_directories("${PTHREAD_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PTHREAD_INCLUDE_DIR}") - endif(PTHREAD_INCLUDE_DIR) - mark_as_advanced(FORCE PTHREAD_LIBRARIES PTHREAD_INCLUDE_DIR) - check_include_file("pthread.h" HAVE_PTHREAD_H) - if(NOT HAVE_PTHREAD_H) - message(FATAL_ERROR - "ZoneMinder requires pthread headers - check that pthread development packages are installed") - endif(NOT HAVE_PTHREAD_H) -else(PTHREAD_LIBRARIES) - message(FATAL_ERROR - "ZoneMinder requires pthread but it was not found on your system") -endif(PTHREAD_LIBRARIES) + set(HAVE_LIBPTHREAD 1) + list(APPEND ZM_BIN_LIBS "${PTHREAD_LIBRARIES}") + find_path(PTHREAD_INCLUDE_DIR pthread.h) + if(PTHREAD_INCLUDE_DIR) + include_directories("${PTHREAD_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PTHREAD_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PTHREAD_LIBRARIES PTHREAD_INCLUDE_DIR) + check_include_file("pthread.h" HAVE_PTHREAD_H) + if(NOT HAVE_PTHREAD_H) + message(FATAL_ERROR "ZoneMinder requires pthread headers - check that pthread development packages are installed") + endif() +else() + message(FATAL_ERROR "ZoneMinder requires pthread but it was not found on your system") +endif() # pcre (using find_library and find_path) find_library(PCRE_LIBRARIES pcre) if(PCRE_LIBRARIES) - set(HAVE_LIBPCRE 1) - list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") - find_path(PCRE_INCLUDE_DIR pcre.h) - if(PCRE_INCLUDE_DIR) - include_directories("${PCRE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") - endif(PCRE_INCLUDE_DIR) - mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) - check_include_file("pcre.h" HAVE_PCRE_H) - set(optlibsfound "${optlibsfound} PCRE") -else(PCRE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} PCRE") -endif(PCRE_LIBRARIES) + set(HAVE_LIBPCRE 1) + list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") + find_path(PCRE_INCLUDE_DIR pcre.h) + if(PCRE_INCLUDE_DIR) + include_directories("${PCRE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) + check_include_file("pcre.h" HAVE_PCRE_H) + set(optlibsfound "${optlibsfound} PCRE") +else() + set(optlibsnotfound "${optlibsnotfound} PCRE") +endif() # gcrypt (using find_library and find_path) find_library(GCRYPT_LIBRARIES gcrypt) if(GCRYPT_LIBRARIES) - set(HAVE_LIBGCRYPT 1) - list(APPEND ZM_BIN_LIBS "${GCRYPT_LIBRARIES}") - find_path(GCRYPT_INCLUDE_DIR gcrypt.h) - if(GCRYPT_INCLUDE_DIR) - include_directories("${GCRYPT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GCRYPT_INCLUDE_DIR}") - endif(GCRYPT_INCLUDE_DIR) - mark_as_advanced(FORCE GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) - check_include_file("gcrypt.h" HAVE_GCRYPT_H) - set(optlibsfound "${optlibsfound} GCrypt") -else(GCRYPT_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} GCrypt") -endif(GCRYPT_LIBRARIES) + set(HAVE_LIBGCRYPT 1) + list(APPEND ZM_BIN_LIBS "${GCRYPT_LIBRARIES}") + find_path(GCRYPT_INCLUDE_DIR gcrypt.h) + if(GCRYPT_INCLUDE_DIR) + include_directories("${GCRYPT_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${GCRYPT_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) + check_include_file("gcrypt.h" HAVE_GCRYPT_H) + set(optlibsfound "${optlibsfound} GCrypt") +else() + set(optlibsnotfound "${optlibsnotfound} GCrypt") +endif() # mysqlclient (using find_library and find_path) find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) - set(HAVE_LIBMYSQLCLIENT 1) - list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") - find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) - if(MYSQLCLIENT_INCLUDE_DIR) - include_directories("${MYSQLCLIENT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") - endif(MYSQLCLIENT_INCLUDE_DIR) - mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) - check_include_file("mysql.h" HAVE_MYSQL_H) - if(NOT HAVE_MYSQL_H) - message(FATAL_ERROR - "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") - endif(NOT HAVE_MYSQL_H) -else(MYSQLCLIENT_LIBRARIES) - message(FATAL_ERROR - "ZoneMinder requires mysqlclient but it was not found on your system") -endif(MYSQLCLIENT_LIBRARIES) - -# x264 (using find_library and find_path) -find_library(X264_LIBRARIES x264) -if(X264_LIBRARIES) - set(HAVE_LIBX264 1) - list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}") - find_path(X264_INCLUDE_DIR x264.h) - if(X264_INCLUDE_DIR) - include_directories("${X264_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}") - endif(X264_INCLUDE_DIR) - mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR) - check_include_files("stdint.h;x264.h" HAVE_X264_H) - set(optlibsfound "${optlibsfound} x264") -else(X264_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} x264") -endif(X264_LIBRARIES) - -# mp4v2 (using find_library and find_path) -find_library(MP4V2_LIBRARIES mp4v2) -if(MP4V2_LIBRARIES) - set(HAVE_LIBMP4V2 1) - list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}") - - # mp4v2/mp4v2.h - find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H) - endif(MP4V2_INCLUDE_DIR) - - # mp4v2.h - find_path(MP4V2_INCLUDE_DIR mp4v2.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4v2.h" HAVE_MP4V2_H) - endif(MP4V2_INCLUDE_DIR) - - # mp4.h - find_path(MP4V2_INCLUDE_DIR mp4.h) - if(MP4V2_INCLUDE_DIR) - include_directories("${MP4V2_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") - check_include_file("mp4.h" HAVE_MP4_H) - endif(MP4V2_INCLUDE_DIR) - - mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR) - set(optlibsfound "${optlibsfound} mp4v2") -else(MP4V2_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} mp4v2") -endif(MP4V2_LIBRARIES) + set(HAVE_LIBMYSQLCLIENT 1) + list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") + find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) + if(MYSQLCLIENT_INCLUDE_DIR) + include_directories("${MYSQLCLIENT_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) + check_include_file("mysql.h" HAVE_MYSQL_H) + if(NOT HAVE_MYSQL_H) + message(FATAL_ERROR "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") + endif() +else() + message(FATAL_ERROR "ZoneMinder requires mysqlclient but it was not found on your system") +endif() set(PATH_FFMPEG "") set(OPT_FFMPEG "no") # avformat (using find_library and find_path) find_library(AVFORMAT_LIBRARIES avformat) if(AVFORMAT_LIBRARIES) - set(HAVE_LIBAVFORMAT 1) - list(APPEND ZM_BIN_LIBS "${AVFORMAT_LIBRARIES}") - find_path(AVFORMAT_INCLUDE_DIR "libavformat/avformat.h" /usr/include/ffmpeg) - if(AVFORMAT_INCLUDE_DIR) - include_directories("${AVFORMAT_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVFORMAT_INCLUDE_DIR}") - endif(AVFORMAT_INCLUDE_DIR) - mark_as_advanced(FORCE AVFORMAT_LIBRARIES AVFORMAT_INCLUDE_DIR) - check_include_file("libavformat/avformat.h" HAVE_LIBAVFORMAT_AVFORMAT_H) - set(optlibsfound "${optlibsfound} AVFormat") -else(AVFORMAT_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVFormat") -endif(AVFORMAT_LIBRARIES) + set(HAVE_LIBAVFORMAT 1) + list(APPEND ZM_BIN_LIBS "${AVFORMAT_LIBRARIES}") + find_path(AVFORMAT_INCLUDE_DIR "libavformat/avformat.h" /usr/include/ffmpeg) + if(AVFORMAT_INCLUDE_DIR) + include_directories("${AVFORMAT_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVFORMAT_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE AVFORMAT_LIBRARIES AVFORMAT_INCLUDE_DIR) + check_include_file("libavformat/avformat.h" HAVE_LIBAVFORMAT_AVFORMAT_H) + set(optlibsfound "${optlibsfound} AVFormat") +else() + set(optlibsnotfound "${optlibsnotfound} AVFormat") +endif() # avcodec (using find_library and find_path) find_library(AVCODEC_LIBRARIES avcodec) if(AVCODEC_LIBRARIES) - set(HAVE_LIBAVCODEC 1) - list(APPEND ZM_BIN_LIBS "${AVCODEC_LIBRARIES}") - find_path(AVCODEC_INCLUDE_DIR "libavcodec/avcodec.h" /usr/include/ffmpeg) - if(AVCODEC_INCLUDE_DIR) - include_directories("${AVCODEC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVCODEC_INCLUDE_DIR}") - endif(AVCODEC_INCLUDE_DIR) - mark_as_advanced(FORCE AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIR) - check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H) - set(optlibsfound "${optlibsfound} AVCodec") -else(AVCODEC_LIBRARIES) + set(HAVE_LIBAVCODEC 1) + list(APPEND ZM_BIN_LIBS "${AVCODEC_LIBRARIES}") + find_path(AVCODEC_INCLUDE_DIR "libavcodec/avcodec.h" /usr/include/ffmpeg) + if(AVCODEC_INCLUDE_DIR) + include_directories("${AVCODEC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVCODEC_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIR) + check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H) + set(optlibsfound "${optlibsfound} AVCodec") +else() message(WARNING "\nWhile it should be possible to build ZM without AVCODEC the result will pretty useless.") - set(optlibsnotfound "${optlibsnotfound} AVCodec") -endif(AVCODEC_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} AVCodec") +endif() # avdevice (using find_library and find_path) find_library(AVDEVICE_LIBRARIES avdevice) if(AVDEVICE_LIBRARIES) - set(HAVE_LIBAVDEVICE 1) - list(APPEND ZM_BIN_LIBS "${AVDEVICE_LIBRARIES}") - find_path(AVDEVICE_INCLUDE_DIR "libavdevice/avdevice.h" /usr/include/ffmpeg) - if(AVDEVICE_INCLUDE_DIR) - include_directories("${AVDEVICE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVDEVICE_INCLUDE_DIR}") - endif(AVDEVICE_INCLUDE_DIR) - mark_as_advanced(FORCE AVDEVICE_LIBRARIES AVDEVICE_INCLUDE_DIR) - check_include_file("libavdevice/avdevice.h" HAVE_LIBAVDEVICE_AVDEVICE_H) - set(optlibsfound "${optlibsfound} AVDevice") -else(AVDEVICE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVDevice") -endif(AVDEVICE_LIBRARIES) + set(HAVE_LIBAVDEVICE 1) + list(APPEND ZM_BIN_LIBS "${AVDEVICE_LIBRARIES}") + find_path(AVDEVICE_INCLUDE_DIR "libavdevice/avdevice.h" /usr/include/ffmpeg) + if(AVDEVICE_INCLUDE_DIR) + include_directories("${AVDEVICE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVDEVICE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE AVDEVICE_LIBRARIES AVDEVICE_INCLUDE_DIR) + check_include_file("libavdevice/avdevice.h" HAVE_LIBAVDEVICE_AVDEVICE_H) + set(optlibsfound "${optlibsfound} AVDevice") +else() + set(optlibsnotfound "${optlibsnotfound} AVDevice") +endif() # avutil (using find_library and find_path) find_library(AVUTIL_LIBRARIES avutil) if(AVUTIL_LIBRARIES) - set(HAVE_LIBAVUTIL 1) - list(APPEND ZM_BIN_LIBS "${AVUTIL_LIBRARIES}") - find_path(AVUTIL_INCLUDE_DIR "libavutil/avutil.h" /usr/include/ffmpeg) - if(AVUTIL_INCLUDE_DIR) - include_directories("${AVUTIL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVUTIL_INCLUDE_DIR}") - endif(AVUTIL_INCLUDE_DIR) - mark_as_advanced(FORCE AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIR) - check_include_file("libavutil/avutil.h" HAVE_LIBAVUTIL_AVUTIL_H) - check_include_file("libavutil/mathematics.h" HAVE_LIBAVUTIL_MATHEMATICS_H) - check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) - set(optlibsfound "${optlibsfound} AVUtil") -else(AVUTIL_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVUtil") -endif(AVUTIL_LIBRARIES) + set(HAVE_LIBAVUTIL 1) + list(APPEND ZM_BIN_LIBS "${AVUTIL_LIBRARIES}") + find_path(AVUTIL_INCLUDE_DIR "libavutil/avutil.h" /usr/include/ffmpeg) + if(AVUTIL_INCLUDE_DIR) + include_directories("${AVUTIL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVUTIL_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIR) + check_include_file("libavutil/avutil.h" HAVE_LIBAVUTIL_AVUTIL_H) + check_include_file("libavutil/mathematics.h" HAVE_LIBAVUTIL_MATHEMATICS_H) + check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) + set(optlibsfound "${optlibsfound} AVUtil") +else() + set(optlibsnotfound "${optlibsnotfound} AVUtil") +endif() # swscale (using find_library and find_path) find_library(SWSCALE_LIBRARIES swscale) if(SWSCALE_LIBRARIES) - set(HAVE_LIBSWSCALE 1) - list(APPEND ZM_BIN_LIBS "${SWSCALE_LIBRARIES}") - find_path(SWSCALE_INCLUDE_DIR "libswscale/swscale.h" /usr/include/ffmpeg) - if(SWSCALE_INCLUDE_DIR) - include_directories("${SWSCALE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${SWSCALE_INCLUDE_DIR}") - endif(SWSCALE_INCLUDE_DIR) - mark_as_advanced(FORCE SWSCALE_LIBRARIES SWSCALE_INCLUDE_DIR) - check_include_file("libswscale/swscale.h" HAVE_LIBSWSCALE_SWSCALE_H) - set(optlibsfound "${optlibsfound} SWScale") -else(SWSCALE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} SWScale") -endif(SWSCALE_LIBRARIES) + set(HAVE_LIBSWSCALE 1) + list(APPEND ZM_BIN_LIBS "${SWSCALE_LIBRARIES}") + find_path(SWSCALE_INCLUDE_DIR "libswscale/swscale.h" /usr/include/ffmpeg) + if(SWSCALE_INCLUDE_DIR) + include_directories("${SWSCALE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${SWSCALE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE SWSCALE_LIBRARIES SWSCALE_INCLUDE_DIR) + check_include_file("libswscale/swscale.h" HAVE_LIBSWSCALE_SWSCALE_H) + set(optlibsfound "${optlibsfound} SWScale") +else() + set(optlibsnotfound "${optlibsnotfound} SWScale") +endif() # SWresample (using find_library and find_path) find_library(SWRESAMPLE_LIBRARIES swresample) @@ -624,11 +546,11 @@ if(SWRESAMPLE_LIBRARIES) if(SWRESAMPLE_INCLUDE_DIR) include_directories("${SWRESAMPLE_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}") - endif(SWRESAMPLE_INCLUDE_DIR) + endif() mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) set(optlibsfound "${optlibsfound} SWResample") -else(SWRESAMPLE_LIBRARIES) +else() set(optlibsnotfound "${optlibsnotfound} SWResample") # AVresample (using find_library and find_path) @@ -640,79 +562,84 @@ else(SWRESAMPLE_LIBRARIES) if(AVRESAMPLE_INCLUDE_DIR) include_directories("${AVRESAMPLE_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") - endif(AVRESAMPLE_INCLUDE_DIR) + endif() mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) set(optlibsfound "${optlibsfound} AVResample") - else(AVRESAMPLE_LIBRARIES) + else() set(optlibsnotfound "${optlibsnotfound} AVResample") - endif(AVRESAMPLE_LIBRARIES) - -endif(SWRESAMPLE_LIBRARIES) + endif() +endif() # Find the path to the ffmpeg executable -find_program(FFMPEG_EXECUTABLE - NAMES ffmpeg avconv - PATH_SUFFIXES ffmpeg) +find_program(FFMPEG_EXECUTABLE + NAMES ffmpeg avconv + PATH_SUFFIXES ffmpeg) if(FFMPEG_EXECUTABLE) - set(PATH_FFMPEG "${FFMPEG_EXECUTABLE}") - set(OPT_FFMPEG "yes") - mark_as_advanced(FFMPEG_EXECUTABLE) -endif(FFMPEG_EXECUTABLE) + set(PATH_FFMPEG "${FFMPEG_EXECUTABLE}") + set(OPT_FFMPEG "yes") + mark_as_advanced(FFMPEG_EXECUTABLE) +endif() # Do not check for libvlc if ZM_NO_LIBVLC is on if(NOT ZM_NO_LIBVLC) - # libvlc (using find_library and find_path) - find_library(LIBVLC_LIBRARIES vlc) - if(LIBVLC_LIBRARIES) - set(HAVE_LIBVLC 1) - #list(APPEND ZM_BIN_LIBS "${LIBVLC_LIBRARIES}") - find_path(LIBVLC_INCLUDE_DIR "vlc/vlc.h") - if(LIBVLC_INCLUDE_DIR) - include_directories("${LIBVLC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${LIBVLC_INCLUDE_DIR}") - endif(LIBVLC_INCLUDE_DIR) - mark_as_advanced(FORCE LIBVLC_LIBRARIES LIBVLC_INCLUDE_DIR) - check_include_file("vlc/vlc.h" HAVE_VLC_VLC_H) - set(optlibsfound "${optlibsfound} libVLC") - else(LIBVLC_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} libVLC") - endif(LIBVLC_LIBRARIES) -endif(NOT ZM_NO_LIBVLC) + # libvlc (using find_library and find_path) + find_library(LIBVLC_LIBRARIES vlc) + if(LIBVLC_LIBRARIES) + set(HAVE_LIBVLC 1) + #list(APPEND ZM_BIN_LIBS "${LIBVLC_LIBRARIES}") + find_path(LIBVLC_INCLUDE_DIR "vlc/vlc.h") + if(LIBVLC_INCLUDE_DIR) + include_directories("${LIBVLC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${LIBVLC_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE LIBVLC_LIBRARIES LIBVLC_INCLUDE_DIR) + check_include_file("vlc/vlc.h" HAVE_VLC_VLC_H) + set(optlibsfound "${optlibsfound} libVLC") + else() + set(optlibsnotfound "${optlibsnotfound} libVLC") + endif() +endif() if(NOT ZM_NO_LIBVNC) - # libvncclient (using find_library and find_path) - find_library(LIBVNC_LIBRARIES vncclient) - if(LIBVNC_LIBRARIES) - set(HAVE_LIBVNC 1) - #list(APPEND ZM_BIN_LIBS "${LIBVNC_LIBRARIES}") - find_path(LIBVNC_INCLUDE_DIR "rfb/rfb.h") - if(LIBVNC_INCLUDE_DIR) - include_directories("${LIBVNC_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${LIBVNC_INCLUDE_DIR}") - endif(LIBVNC_INCLUDE_DIR) - mark_as_advanced(FORCE LIBVNC_LIBRARIES LIBVNC_INCLUDE_DIR) - check_include_file("rfb/rfb.h" HAVE_RFB_RFB_H) - set(optlibsfound "${optlibsfound} libVNC") - else(LIBVNC_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} libVNC") - endif(LIBVNC_LIBRARIES) -endif(NOT ZM_NO_LIBVNC) + # libvncclient (using find_library and find_path) + find_library(LIBVNC_LIBRARIES vncclient) + if(LIBVNC_LIBRARIES) + set(HAVE_LIBVNC 1) + #list(APPEND ZM_BIN_LIBS "${LIBVNC_LIBRARIES}") + find_path(LIBVNC_INCLUDE_DIR "rfb/rfb.h") + if(LIBVNC_INCLUDE_DIR) + include_directories("${LIBVNC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${LIBVNC_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE LIBVNC_LIBRARIES LIBVNC_INCLUDE_DIR) + check_include_file("rfb/rfb.h" HAVE_RFB_RFB_H) + set(optlibsfound "${optlibsfound} libVNC") + else() + set(optlibsnotfound "${optlibsnotfound} libVNC") + endif() +endif() #find_package(Boost 1.36.0) #if(Boost_FOUND) - #include_directories(${Boost_INCLUDE_DIRS}) - ##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") - #list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") +#include_directories(${Boost_INCLUDE_DIRS}) +##set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") +#list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") #endif() + +if(NOT ZM_NO_RTSPSERVER) + set(HAVE_RTSP_SERVER 1) +else() + set(HAVE_RTSP_SERVER 0) +endif() + # # *** END OF LIBRARY CHECKS *** # Check for gnutls or crypto if((NOT HAVE_LIBCRYPTO) AND (NOT HAVE_LIBGNUTLS)) - message(FATAL_ERROR - "ZoneMinder requires crypto or gnutls but none were found on your system") -endif((NOT HAVE_LIBCRYPTO) AND (NOT HAVE_LIBGNUTLS)) + message(FATAL_ERROR "ZoneMinder requires crypto or gnutls but none were found on your system") +endif() # Check for V4L header files and enable ZM_HAS_V4L, ZM_HAS_V4L1, ZM_HAS_V4L2 accordingly # Setting to zeros first is required because ZM uses #define for these @@ -720,150 +647,144 @@ set(ZM_HAS_V4L 0) set(ZM_HAS_V4L1 0) set(ZM_HAS_V4L2 0) if(HAVE_LINUX_VIDEODEV_H OR HAVE_LIBV4L1_VIDEODEV_H) - set(ZM_HAS_V4L 1) - set(ZM_HAS_V4L1 1) -endif(HAVE_LINUX_VIDEODEV_H OR HAVE_LIBV4L1_VIDEODEV_H) + set(ZM_HAS_V4L 1) + set(ZM_HAS_V4L1 1) +endif() if(HAVE_LINUX_VIDEODEV2_H) - set(ZM_HAS_V4L 1) - set(ZM_HAS_V4L2 1) -endif(HAVE_LINUX_VIDEODEV2_H) -if((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) - message(AUTHOR_WARNING - "Video 4 Linux headers weren't found - Analog and USB camera support will not be available") -endif((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) + set(ZM_HAS_V4L 1) + set(ZM_HAS_V4L2 1) +endif() +if((NOT HAVE_LINUX_VIDEODEV_H) + AND (NOT HAVE_LIBV4L1_VIDEODEV_H) + AND (NOT HAVE_LINUX_VIDEODEV2_H)) + message(AUTHOR_WARNING "Video 4 Linux headers weren't found - Analog and USB camera support will not be available") +endif() # Check for PCRE and enable ZM_PCRE accordingly set(ZM_PCRE 0) if(HAVE_LIBPCRE AND HAVE_PCRE_H) - set(ZM_PCRE 1) -endif(HAVE_LIBPCRE AND HAVE_PCRE_H) + set(ZM_PCRE 1) +endif() # Check for mmap and enable in all components set(ZM_MEM_MAPPED 0) set(ENABLE_MMAP no) if(NOT ZM_NO_MMAP) - set(ZM_MEM_MAPPED 1) - set(ENABLE_MMAP yes) - set(ZM_MMAP_PERLPACKAGE "Sys::Mmap") -endif(NOT ZM_NO_MMAP) + set(ZM_MEM_MAPPED 1) + set(ENABLE_MMAP yes) + set(ZM_MMAP_PERLPACKAGE "Sys::Mmap") +endif() # Check for the ONVIF flag and enable ZM_HAS_ONVIF accordingly set(ZM_HAS_ONVIF 0) if(ZM_ONVIF) - set(ZM_HAS_ONVIF 1) -endif(ZM_ONVIF) + set(ZM_HAS_ONVIF 1) +endif() # Check for authentication functions if(HAVE_OPENSSL_MD5_H) - set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" - HAVE_MD5_OPENSSL) -endif(HAVE_OPENSSL_MD5_H) + set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + check_prototype_definition( + MD5 + "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" + HAVE_MD5_OPENSSL) +endif() if(HAVE_GNUTLS_GNUTLS_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - gnutls_fingerprint - "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" - HAVE_DECL_GNUTLS_FINGERPRINT) -endif(HAVE_GNUTLS_GNUTLS_H) + set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") + set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") + check_prototype_definition( + gnutls_fingerprint + "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" + HAVE_DECL_GNUTLS_FINGERPRINT) +endif() if(NOT HAVE_DECL_GNUTLS_FINGERPRINT AND HAVE_MD5_OPENSSL) - set(HAVE_DECL_MD5 1) -endif(NOT HAVE_DECL_GNUTLS_FINGERPRINT AND HAVE_MD5_OPENSSL) + set(HAVE_DECL_MD5 1) +endif() if((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) - message(AUTHOR_WARNING - "ZoneMinder requires a working MD5 function for hashed authentication but - none were found - hashed authentication will not be available") -endif((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) + message(AUTHOR_WARNING + "ZoneMinder requires a working MD5 function for hashed authentication but + none were found - hashed authentication will not be available") +endif() # Dirty fix for zm_user only using openssl's md5 if gnutls and gcrypt are not available. # This needs to be fixed in zm_user.[h,cpp] but such fix will also require changes to configure.ac if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) - set(HAVE_GCRYPT_H 0) - set(HAVE_GNUTLS_OPENSSL_H 0) -endif(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) + set(HAVE_GCRYPT_H 0) + set(HAVE_GNUTLS_OPENSSL_H 0) +endif() # Check for Perl find_package(Perl) if(NOT PERL_FOUND) - message(FATAL_ERROR - "ZoneMinder requires Perl 5.6.0 or newer but it was not found on your system") -endif(NOT PERL_FOUND) + message(FATAL_ERROR "ZoneMinder requires Perl 5.6.0 or newer but it was not found on your system") +endif() # Checking for perl modules requires FindPerlModules.cmake # Check all required modules at once # TODO: Add checking for the optional modules find_package( - PerlModules COMPONENTS Sys::Syslog DBI DBD::mysql - Getopt::Long Time::HiRes Date::Manip LWP::UserAgent - ExtUtils::MakeMaker ${ZM_MMAP_PERLPACKAGE}) + PerlModules COMPONENTS Sys::Syslog DBI DBD::mysql + Getopt::Long Time::HiRes Date::Manip LWP::UserAgent + ExtUtils::MakeMaker ${ZM_MMAP_PERLPACKAGE}) if(NOT PERLMODULES_FOUND) - message(FATAL_ERROR - "Not all required perl modules were found on your system") -endif(NOT PERLMODULES_FOUND) + message(WARNING "Not all required perl modules were found on your system") +endif() # Attempt to check which user apache (or other web server) runs on by # searching for a user beginning with apache or www and then cutting the user # from the first matching user line if(ZM_WEB_USER STREQUAL "") - # Check for a user matching ^apache and cut the username from the - # userline in the first match - file(STRINGS "/etc/passwd" userline_apache REGEX "^apache") - file(STRINGS "/etc/passwd" userline_www REGEX "^www") - if(NOT (userline_apache STREQUAL "")) - execute_process( - COMMAND echo ${userline_apache} - COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER - OUTPUT_STRIP_TRAILING_WHITESPACE) - elseif(NOT (userline_www STREQUAL "")) - execute_process( - COMMAND echo ${userline_www} - COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif(NOT (userline_apache STREQUAL "")) - message(STATUS "Detected web server user: ${ZM_WEB_USER}") -endif(ZM_WEB_USER STREQUAL "") + # Check for a user matching ^apache and cut the username from the + # userline in the first match + file(STRINGS "/etc/passwd" userline_apache REGEX "^apache") + file(STRINGS "/etc/passwd" userline_www REGEX "^www") + if(NOT (userline_apache STREQUAL "")) + execute_process( + COMMAND echo ${userline_apache} + COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER + OUTPUT_STRIP_TRAILING_WHITESPACE) + elseif(NOT (userline_www STREQUAL "")) + execute_process( + COMMAND echo ${userline_www} + COMMAND cut -d: -f1 OUTPUT_VARIABLE ZM_WEB_USER + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + message(STATUS "Detected web server user: ${ZM_WEB_USER}") +endif() # Check if webgroup contains anything. If not, use the web user as the web group if(NOT ZM_WEB_GROUP) - set(ZM_WEB_GROUP ${ZM_WEB_USER}) -endif(NOT ZM_WEB_GROUP) + set(ZM_WEB_GROUP ${ZM_WEB_USER}) +endif() message(STATUS "Using web user: ${ZM_WEB_USER}") message(STATUS "Using web group: ${ZM_WEB_GROUP}") if(WITH_SYSTEMD) - # Check for polkit - find_package(Polkit) - if(NOT POLKIT_FOUND) - message(FATAL_ERROR - "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") - endif(NOT POLKIT_FOUND) -endif(WITH_SYSTEMD) + # Check for polkit + find_package(Polkit) + if(NOT POLKIT_FOUND) + message(WARNING "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") + endif() +endif() # Find the path to an arp compatible executable if(ZM_PATH_ARP STREQUAL "") - find_program(ARP_EXECUTABLE arp) + find_program(ARP_EXECUTABLE arp) + if(ARP_EXECUTABLE) + set(ZM_PATH_ARP "${ARP_EXECUTABLE}") + mark_as_advanced(ARP_EXECUTABLE) + else() + find_program(ARP_EXECUTABLE ip) if(ARP_EXECUTABLE) - set(ZM_PATH_ARP "${ARP_EXECUTABLE}") - mark_as_advanced(ARP_EXECUTABLE) - else(ARP_EXECUTABLE) - find_program(ARP_EXECUTABLE ip) - if(ARP_EXECUTABLE) - set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh") - mark_as_advanced(ARP_EXECUTABLE) - endif(ARP_EXECUTABLE) - endif(ARP_EXECUTABLE) - if(ARP_EXECUTABLE-NOTFOUND) - message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function." ) - endif(ARP_EXECUTABLE-NOTFOUND) -endif(ZM_PATH_ARP STREQUAL "") + set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh") + mark_as_advanced(ARP_EXECUTABLE) + endif() + endif() + if(ARP_EXECUTABLE-NOTFOUND) + message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function.") + endif() +endif() # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") @@ -880,15 +801,15 @@ set(WEB_USER "${ZM_WEB_USER}") set(WEB_GROUP "${ZM_WEB_GROUP}") set(ZM_DB_TYPE "mysql") if(ZM_PERL_SEARCH_PATH) - set(EXTRA_PERL_LIB "use lib '${ZM_PERL_SEARCH_PATH}'; # Include custom perl install path") -else(ZM_PERL_SEARCH_PATH) - set(EXTRA_PERL_LIB "# Include from system perl paths only") -endif(ZM_PERL_SEARCH_PATH) + set(EXTRA_PERL_LIB "use lib '${ZM_PERL_SEARCH_PATH}'; # Include custom perl install path") +else() + set(EXTRA_PERL_LIB "# Include from system perl paths only") +endif() # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # Generate files from the .in files configure_file(zm.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" @ONLY) @@ -901,26 +822,31 @@ configure_file(zmlinkcontent.sh.in "${CMAKE_CURRENT_BINARY_DIR}/zmlinkcontent.sh include(Pod2Man) # Process subdirectories - -# build a bcrypt static library -set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") -set(BUILD_SHARED_LIBS OFF) -add_subdirectory(src/libbcrypt EXCLUDE_FROM_ALL) -set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}") - +add_subdirectory(dep) add_subdirectory(src) add_subdirectory(scripts) add_subdirectory(db) +add_subdirectory(fonts) add_subdirectory(web) add_subdirectory(misc) add_subdirectory(onvif) +if(BUILD_TEST_SUITE) + message("Building unit tests: Yes") + find_package(Catch2 REQUIRED) + + include(CTest) + add_subdirectory(tests) +else() + message("Building unit tests: No (default)") +endif() + # Process distro subdirectories if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) - add_subdirectory(distros/redhat) -elseif(ZM_TARGET_DISTRO STREQUAL "OS13") - add_subdirectory(distros/opensuse) -endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) + add_subdirectory(distros/redhat) +elseif() + add_subdirectory(distros/opensuse) +endif() # Print optional libraries detection status message(STATUS "Optional libraries found:${optlibsfound}") @@ -928,14 +854,12 @@ message(STATUS "Optional libraries not found:${optlibsnotfound}") # Run ZM configuration generator message(STATUS "Running ZoneMinder configuration generator") -execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE zmconfgen_result) -if(zmconfgen_result EQUAL 0) - message(STATUS - "ZoneMinder configuration generator completed successfully") -else(zmconfgen_result EQUAL 0) - message(FATAL_ERROR - "ZoneMinder configuration generator failed. Exit code: ${zmconfgen_result}") -endif(zmconfgen_result EQUAL 0) +execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE ZMCONFGEN_RESULT) +if(ZMCONFGEN_RESULT EQUAL 0) + message(STATUS "ZoneMinder configuration generator completed successfully") +else() + message(FATAL_ERROR "ZoneMinder configuration generator failed. Exit code: ${zmconfgen_result}") +endif() # Install zm.conf install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" DESTINATION "${ZM_CONFIG_DIR}") @@ -943,17 +867,17 @@ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/conf.d/" DESTINATION "${ZM_CONFIG # Uninstall target configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" - IMMEDIATE @ONLY) + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake" + IMMEDIATE @ONLY) add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) # Configure CCache if available find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) -endif(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif() install(DIRECTORY icons DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/") diff --git a/README.md b/README.md index ab946c04c..20cce499c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde - OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) - Mageia from their default repository - Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) -- Gentoo from their [default repository](https://packages.gentoo.org/packages/www-misc/zoneminder) +- Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder) If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. @@ -70,18 +70,19 @@ Docker is a system to run applications inside isolated containers. ZoneMinder, a Dockerfile contained in this repository. However, there is still work needed to ensure that the main ZM features work properly and are documented. -## Contribution Model and Development +## Contribution Model and Development * Source hosted at [GitHub](https://github.com/ZoneMinder/ZoneMinder/) -* Report issues/questions/feature requests on [GitHub Issues](https://github.com/ZoneMinder/ZoneMinder/issues) +* Report issues at [GitHub Issues](https://github.com/ZoneMinder/ZoneMinder/issues) +* Questions/feature requests in [Slack](https://zoneminder-chat.slack.com/) or [forums](https://forums.zoneminder.com) Pull requests are very welcome! If you would like to contribute, please follow the following steps. While step 3 is optional, it is preferred. 1. Fork the repo 2. Open an issue at our [GitHub Issues Tracker](https://github.com/ZoneMinder/ZoneMinder/issues). - Describe the bug that you've found, or the feature which you're asking for. - Jot down the issue number (e.g. 456) + Follow the issue template to describe the bug or security issue you found. Please note feature + requests or questions should be posted in our user forum or Slack channel. 3. Create your feature branch (`git checkout -b 456-my-new-feature`) 4. Commit your changes (`git commit -am 'Added some feature'`) It is preferred that you 'commit early and often' instead of bunching all diff --git a/cmake/Modules/CheckPlatform.cmake b/cmake/Modules/CheckPlatform.cmake new file mode 100644 index 000000000..6d88efe32 --- /dev/null +++ b/cmake/Modules/CheckPlatform.cmake @@ -0,0 +1,44 @@ +set(HOST_OS "") +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(HOST_OS "linux") +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES ".*(SunOS|Solaris).*") + set(HOST_OS "solaris") + set(SOLARIS 1) +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES ".*BSD.*") + set(HOST_OS "BSD") + set(BSD 1) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(HOST_OS "darwin") +endif() +if(NOT HOST_OS) + message(FATAL_ERROR + "ZoneMinder was unable to deterimine the host OS. Please report this. + Value of CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" ZM_SYSTEM_PROC) + if((ZM_SYSTEM_PROC STREQUAL "") OR (ZM_SYSTEM_PROC STREQUAL "unknown")) + execute_process(COMMAND uname -m OUTPUT_VARIABLE ZM_SYSTEM_PROC ERROR_VARIABLE ZM_SYSTEM_PROC_ERR) + + # maybe make the following error checks fatal + if(ZM_SYSTEM_PROC_ERR) + message(WARNING "\nAn error occurred while attempting to determine the system processor:\n${ZM_SYSTEM_PROC_ERR}") + endif() + if(NOT ZM_SYSTEM_PROC) + message(WARNING "\nUnable to determine the system processor. This may cause a build failure.\n") + endif() + endif() +endif() + +message(STATUS "Detected compiler: ${CMAKE_C_COMPILER}") +if(CMAKE_C_COMPILER MATCHES "gcc" OR CMAKE_C_COMPILER_ID STREQUAL "GNU") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/gcc/settings.cmake) +elseif(CMAKE_C_COMPILER MATCHES "clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/clang/settings.cmake) +else() + message(FATAL_ERROR "No supported compiler found") +endif() diff --git a/cmake/Modules/ConfigureBaseTargets.cmake b/cmake/Modules/ConfigureBaseTargets.cmake new file mode 100644 index 000000000..ed86d35fc --- /dev/null +++ b/cmake/Modules/ConfigureBaseTargets.cmake @@ -0,0 +1,50 @@ +add_library(zm-compile-option-interface INTERFACE) + +# Use -std=c++11 instead of -std=gnu++11 +set(CMAKE_CXX_EXTENSIONS OFF) + +add_library(zm-feature-interface INTERFACE) + +# The cxx_std_* feature flags were only introduced in CMake 3.8 +# Use to old way to specify the required standard level for older CMake versions. +# Remove this once we raise the required CMake version. +if(${CMAKE_VERSION} VERSION_LESS 3.8.0) + set(CMAKE_CXX_STANDARD 11) +else() + target_compile_features(zm-feature-interface + INTERFACE + cxx_std_11) +endif() + +# Interface to set warning levels on targets. +# It gets populated in the compiler specific script. +add_library(zm-warning-interface INTERFACE) + +# Interface which disables all warnings on the target. +add_library(zm-no-warning-interface INTERFACE) +target_compile_options(zm-no-warning-interface + INTERFACE + -w) + +# An interface used by all other interfaces. +add_library(zm-default-interface INTERFACE) +target_link_libraries(zm-default-interface + INTERFACE + zm-compile-option-interface + zm-feature-interface) + +# An interface which provides the flags and definitions +# used by the non-dependency targets. +add_library(zm-core-interface INTERFACE) +target_link_libraries(zm-core-interface + INTERFACE + zm-default-interface + zm-warning-interface) + +# An interface which provides the flags and definitions +# used by the external dependency targets. +add_library(zm-dependency-interface INTERFACE) +target_link_libraries(zm-dependency-interface + INTERFACE + zm-default-interface + zm-no-warning-interface) diff --git a/cmake/compiler/clang/settings.cmake b/cmake/compiler/clang/settings.cmake new file mode 100644 index 000000000..6c5419a94 --- /dev/null +++ b/cmake/compiler/clang/settings.cmake @@ -0,0 +1,42 @@ +target_compile_options(zm-warning-interface + INTERFACE + -Wall + -Wextra + -Wimplicit-fallthrough + -Wno-unused-parameter) + +if(ENABLE_WERROR) + target_compile_options(zm-warning-interface + INTERFACE + -Werror) +endif() + +if(ASAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + target_link_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + message(STATUS "Clang: Enabled AddressSanitizer (ASan)") +endif() + +if(TSAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + target_link_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + message(STATUS "Clang: Enabled ThreadSanitizer (TSan)") +endif() diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake new file mode 100644 index 000000000..097270d51 --- /dev/null +++ b/cmake/compiler/gcc/settings.cmake @@ -0,0 +1,46 @@ +target_compile_options(zm-warning-interface + INTERFACE + -Wall + $<$,5.0>:-Wconditionally-supported> + -Wextra + -Wformat-security + -Wno-cast-function-type + $<$,11>:-Wno-clobbered> + -Wno-unused-parameter + -Woverloaded-virtual) + +if(ENABLE_WERROR) + target_compile_options(zm-warning-interface + INTERFACE + -Werror) +endif() + +if(ASAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + target_link_options(zm-compile-option-interface + INTERFACE + -fno-omit-frame-pointer + -fsanitize=address + -fsanitize-recover=address + -fsanitize-address-use-after-scope) + + message(STATUS "GCC: Enabled AddressSanitizer (ASan)") +endif() + +if(TSAN) + target_compile_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + target_link_options(zm-compile-option-interface + INTERFACE + -fsanitize=thread) + + message(STATUS "GCC: Enabled ThreadSanitizer (TSan)") +endif() diff --git a/db/CMakeLists.txt b/db/CMakeLists.txt index ed215d35f..18f440fc8 100644 --- a/db/CMakeLists.txt +++ b/db/CMakeLists.txt @@ -3,6 +3,7 @@ # Create files from the .in files configure_file(zm_create.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" @ONLY) configure_file(zm_update-1.31.30.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" @ONLY) +configure_file(zm_update-1.35.24.sql.in "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" @ONLY) # Glob all database upgrade scripts file(GLOB dbfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "zm_update-*.sql") @@ -12,6 +13,8 @@ install(FILES ${dbfileslist} DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db # install zm_update-1.31.30.sql install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.31.30.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") +# install zm_update-1.35.24.sql +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_update-1.35.24.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") # install zm_create.sql install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db") diff --git a/db/triggers.sql b/db/triggers.sql index 87c8465b4..58f09bcbf 100644 --- a/db/triggers.sql +++ b/db/triggers.sql @@ -3,10 +3,10 @@ delimiter // DROP TRIGGER IF EXISTS Events_Hour_delete_trigger// CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET HourEvents = GREATEST(COALESCE(HourEvents,1)-1,0), HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -20,10 +20,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -32,10 +32,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Day_delete_trigger// CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET DayEvents = GREATEST(COALESCE(DayEvents,1)-1,0), DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -48,10 +48,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -61,10 +61,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Week_delete_trigger// CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET WeekEvents = GREATEST(COALESCE(WeekEvents,1)-1,0), WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -77,10 +77,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -89,10 +89,10 @@ FOR EACH ROW DROP TRIGGER IF EXISTS Events_Month_delete_trigger// CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month FOR EACH ROW BEGIN - UPDATE Monitors SET + UPDATE Event_Summaries SET MonthEvents = GREATEST(COALESCE(MonthEvents,1)-1,0), MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END; // @@ -105,10 +105,10 @@ FOR EACH ROW set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( diff ) THEN IF ( NEW.MonitorID != OLD.MonitorID ) THEN - UPDATE Monitors SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Monitors.Id=OLD.MonitorId; - UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Event_Summaries.MonitorId=OLD.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Event_Summaries.MonitorId=NEW.MonitorId; ELSE - UPDATE Monitors SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Monitors.Id=NEW.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -126,14 +126,14 @@ BEGIN set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); IF ( NEW.StorageId = OLD.StorageID ) THEN IF ( diff ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Storage.Id = OLD.StorageId; END IF; ELSE IF ( NEW.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Id = NEW.StorageId; + UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Storage.Id = NEW.StorageId; END IF; IF ( OLD.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Storage.Id = OLD.StorageId; END IF; END IF; @@ -145,20 +145,21 @@ BEGIN IF ( NEW.Archived != OLD.Archived ) THEN IF ( NEW.Archived ) THEN INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace); - UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=NEW.MonitorId; + INSERT INTO Event_Summaries (MonitorId,ArchivedEvents,ArchivedEventDiskSpace) VALUES (NEW.MonitorId,1,NEW.DiskSpace) ON DUPLICATE KEY + UPDATE ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0); ELSEIF ( OLD.Archived ) THEN DELETE FROM Events_Archived WHERE EventId=OLD.Id; - UPDATE Monitors + UPDATE Event_Summaries SET ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,0)-1,0), ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; ELSE IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; - UPDATE Monitors SET + UPDATE Event_Summaries SET ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END IF; ELSEIF ( NEW.Archived AND diff ) THEN @@ -166,10 +167,10 @@ BEGIN END IF; IF ( diff ) THEN - UPDATE Monitors + UPDATE Event_Summaries SET TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; @@ -185,17 +186,17 @@ CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events FOR EACH ROW BEGIN - INSERT INTO Events_Hour (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Day (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Week (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - INSERT INTO Events_Month (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0); - UPDATE Monitors SET + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Event_Summaries (MonitorId,HourEvents,DayEvents,WeekEvents,MonthEvents,TotalEvents) VALUES (NEW.MonitorId,1,1,1,1,1) ON DUPLICATE KEY + UPDATE HourEvents = COALESCE(HourEvents,0)+1, DayEvents = COALESCE(DayEvents,0)+1, WeekEvents = COALESCE(WeekEvents,0)+1, MonthEvents = COALESCE(MonthEvents,0)+1, - TotalEvents = COALESCE(TotalEvents,0)+1 - WHERE Id=NEW.MonitorId; + TotalEvents = COALESCE(TotalEvents,0)+1; END; // @@ -205,7 +206,7 @@ CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events FOR EACH ROW BEGIN IF ( OLD.DiskSpace ) THEN - UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Id = OLD.StorageId; + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Storage.Id = OLD.StorageId; END IF; DELETE FROM Events_Hour WHERE EventId=OLD.Id; DELETE FROM Events_Day WHERE EventId=OLD.Id; @@ -213,17 +214,17 @@ BEGIN DELETE FROM Events_Month WHERE EventId=OLD.Id; IF ( OLD.Archived ) THEN DELETE FROM Events_Archived WHERE EventId=OLD.Id; - UPDATE Monitors SET + UPDATE Event_Summaries SET ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,1) - 1,0), ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0), TotalEvents = GREATEST(COALESCE(TotalEvents,1) - 1,0), TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; ELSE - UPDATE Monitors SET + UPDATE Event_Summaries SET TotalEvents = GREATEST(COALESCE(TotalEvents,1)-1,0), TotalEventDiskSpace=GREATEST(COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Id=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; @@ -233,14 +234,14 @@ DROP TRIGGER IF EXISTS Zone_Insert_Trigger// CREATE TRIGGER Zone_Insert_Trigger AFTER INSERT ON Zones FOR EACH ROW BEGIN - UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Id=NEW.MonitorID; + UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Monitors.Id=NEW.MonitorID; END // DROP TRIGGER IF EXISTS Zone_Delete_Trigger// CREATE TRIGGER Zone_Delete_Trigger AFTER DELETE ON Zones FOR EACH ROW BEGIN - UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Id=OLD.MonitorID; + UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Monitors.Id=OLD.MonitorID; END // diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index af4e37136..fdad3f7ac 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -189,8 +189,8 @@ CREATE TABLE `Events` ( `SecondaryStorageId` smallint(5) unsigned default 0, `Name` varchar(64) NOT NULL default '', `Cause` varchar(32) NOT NULL default '', - `StartTime` datetime default NULL, - `EndTime` datetime default NULL, + `StartDateTime` datetime default NULL, + `EndDateTime` datetime default NULL, `Width` smallint(5) unsigned NOT NULL default '0', `Height` smallint(5) unsigned NOT NULL default '0', `Length` decimal(10,2) NOT NULL default '0.00', @@ -216,52 +216,52 @@ CREATE TABLE `Events` ( PRIMARY KEY (`Id`), KEY `Events_MonitorId_idx` (`MonitorId`), KEY `Events_StorageId_idx` (`StorageId`), - KEY `Events_StartTime_idx` (`StartTime`), - KEY `Events_EndTime_DiskSpace` (`EndTime`,`DiskSpace`) + KEY `Events_StartDateTime_idx` (`StartDateTime`), + KEY `Events_EndDateTime_DiskSpace` (`EndDateTime`,`DiskSpace`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Hour`; CREATE TABLE `Events_Hour` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Hour_MonitorId_idx` (`MonitorId`), - KEY `Events_Hour_StartTime_idx` (`StartTime`) + KEY `Events_Hour_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Day`; CREATE TABLE `Events_Day` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Day_MonitorId_idx` (`MonitorId`), - KEY `Events_Day_StartTime_idx` (`StartTime`) + KEY `Events_Day_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Week`; CREATE TABLE `Events_Week` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Week_MonitorId_idx` (`MonitorId`), - KEY `Events_Week_StartTime_idx` (`StartTime`) + KEY `Events_Week_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; DROP TABLE IF EXISTS `Events_Month`; CREATE TABLE `Events_Month` ( `EventId` BIGINT unsigned NOT NULL, `MonitorId` int(10) unsigned NOT NULL, - `StartTime` datetime default NULL, + `StartDateTime` datetime default NULL, `DiskSpace` bigint default NULL, PRIMARY KEY (`EventId`), KEY `Events_Month_MonitorId_idx` (`MonitorId`), - KEY `Events_Month_StartTime_idx` (`StartTime`) + KEY `Events_Month_StartDateTime_idx` (`StartDateTime`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -285,6 +285,7 @@ CREATE TABLE `Filters` ( `UserId` int(10) unsigned, `Query_json` text NOT NULL, `AutoArchive` tinyint(3) unsigned NOT NULL default '0', + `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0', `AutoVideo` tinyint(3) unsigned NOT NULL default '0', `AutoUpload` tinyint(3) unsigned NOT NULL default '0', `AutoEmail` tinyint(3) unsigned NOT NULL default '0', @@ -302,6 +303,7 @@ CREATE TABLE `Filters` ( `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', `Concurrent` tinyint(1) unsigned NOT NULL default '0', + `LockRows` tinyint(1) unsigned NOT NULL default '0', PRIMARY KEY (`Id`), KEY `Name` (`Name`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -314,6 +316,7 @@ DROP TABLE IF EXISTS `Frames`; CREATE TABLE `Frames` ( `Id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `EventId` BIGINT UNSIGNED NOT NULL default '0', + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, `FrameId` int(10) unsigned NOT NULL default '0', `Type` enum('Normal','Bulk','Alarm') NOT NULL default 'Normal', `TimeStamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, @@ -334,6 +337,7 @@ CREATE TABLE `Groups` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', `ParentId` int(10) unsigned, + FOREIGN KEY (`ParentId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -345,7 +349,9 @@ DROP TABLE IF EXISTS `Groups_Monitors`; CREATE TABLE `Groups_Monitors` ( `Id` INT(10) unsigned NOT NULL auto_increment, `GroupId` int(10) unsigned NOT NULL, + FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE, `MonitorId` int(10) unsigned NOT NULL, + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -446,6 +452,7 @@ CREATE TABLE `Monitors` ( `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket','VNC') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', + `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '', @@ -463,6 +470,7 @@ CREATE TABLE `Monitors` ( `Port` varchar(8) NOT NULL default '', `SubPath` varchar(64) NOT NULL default '', `Path` varchar(255), + `SecondPath` varchar(255), `Options` varchar(255), `User` varchar(64), `Pass` varchar(64), @@ -476,7 +484,8 @@ CREATE TABLE `Monitors` ( `DecoderHWAccelDevice` varchar(255), `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , `VideoWriter` TINYINT NOT NULL DEFAULT '0', - `OutputCodec` enum('h264','mjpeg','mpeg1','mpeg2'), + `OutputCodec` int(10) unsigned NOT NULL default 0, + `Encoder` varchar(32), `OutputContainer` enum('auto','mp4','mkv'), `EncoderParameters` TEXT, `RecordAudio` TINYINT NOT NULL DEFAULT '0', @@ -490,11 +499,12 @@ CREATE TABLE `Monitors` ( `LabelX` smallint(5) unsigned NOT NULL default '0', `LabelY` smallint(5) unsigned NOT NULL default '0', `LabelSize` smallint(5) unsigned NOT NULL DEFAULT '1', - `ImageBufferCount` smallint(5) unsigned NOT NULL default '100', - `WarmupCount` smallint(5) unsigned NOT NULL default '25', + `ImageBufferCount` smallint(5) unsigned NOT NULL default '3', + `MaxImageBufferCount` smallint(5) unsigned NOT NULL default '0', + `WarmupCount` smallint(5) unsigned NOT NULL default '0', `PreEventCount` smallint(5) unsigned NOT NULL default '10', `PostEventCount` smallint(5) unsigned NOT NULL default '10', - `StreamReplayBuffer` int(10) unsigned NOT NULL default '1000', + `StreamReplayBuffer` int(10) unsigned NOT NULL default '0', `AlarmFrameCount` smallint(5) unsigned NOT NULL default '1', `SectionLength` int(10) unsigned NOT NULL default '600', `MinSectionLength` int(10) unsigned NOT NULL default '10', @@ -516,6 +526,7 @@ CREATE TABLE `Monitors` ( `TrackDelay` smallint(5) unsigned, `ReturnLocation` tinyint(3) NOT NULL default '-1', `ReturnDelay` smallint(5) unsigned, + `ModectDuringPTZ` tinyint(3) unsigned NOT NULL default '0', `DefaultRate` smallint(5) unsigned NOT NULL default '100', `DefaultScale` smallint(5) unsigned NOT NULL default '100', `DefaultCodec` enum('auto','MP4','MJPEG') NOT NULL default 'auto', @@ -524,20 +535,13 @@ CREATE TABLE `Monitors` ( `WebColour` varchar(32) NOT NULL default 'red', `Exif` tinyint(1) unsigned NOT NULL default '0', `Sequence` smallint(5) unsigned default NULL, - `TotalEvents` int(10) default NULL, - `TotalEventDiskSpace` bigint default NULL, - `HourEvents` int(10) default NULL, - `HourEventDiskSpace` bigint default NULL, - `DayEvents` int(10) default NULL, - `DayEventDiskSpace` bigint default NULL, - `WeekEvents` int(10) default NULL, - `WeekEventDiskSpace` bigint default NULL, - `MonthEvents` int(10) default NULL, - `MonthEventDiskSpace` bigint default NULL, - `ArchivedEvents` int(10) default NULL, - `ArchivedEventDiskSpace` bigint default NULL, `ZoneCount` TINYINT NOT NULL DEFAULT 0, `Refresh` int(10) unsigned default NULL, + `Latitude` DECIMAL(10,8), + `Longitude` DECIMAL(10,8), + `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, + `RTSPStreamName` varchar(255) NOT NULL default '', + `Importance` enum('Not','Less','Normal'), PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -551,7 +555,26 @@ CREATE TABLE `Monitor_Status` ( `AnalysisFPS` DECIMAL(5,2) NOT NULL default 0, `CaptureBandwidth` INT NOT NULL default 0, PRIMARY KEY (`MonitorId`) -) ENGINE=MEMORY; +) ENGINE=@ZM_MYSQL_ENGINE@; + +DROP TABLE IF EXISTS `Event_Summaries`; +CREATE TABLE `Event_Summaries` ( + `MonitorId` int(10) unsigned NOT NULL, + `TotalEvents` int(10) default NULL, + `TotalEventDiskSpace` bigint default NULL, + `HourEvents` int(10) default NULL, + `HourEventDiskSpace` bigint default NULL, + `DayEvents` int(10) default NULL, + `DayEventDiskSpace` bigint default NULL, + `WeekEvents` int(10) default NULL, + `WeekEventDiskSpace` bigint default NULL, + `MonthEvents` int(10) default NULL, + `MonthEventDiskSpace` bigint default NULL, + `ArchivedEvents` int(10) default NULL, + `ArchivedEventDiskSpace` bigint default NULL, + PRIMARY KEY (`MonitorId`) +) ENGINE=@ZM_MYSQL_ENGINE@; + -- -- Table structure for table `States` -- PP - Added IsActive to track custom run states @@ -607,8 +630,11 @@ DROP TABLE IF EXISTS `Stats`; CREATE TABLE `Stats` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, `MonitorId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, `ZoneId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`ZoneId`) REFERENCES `Zones` (`Id`) ON DELETE CASCADE, `EventId` BIGINT UNSIGNED NOT NULL, + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, `FrameId` int(10) unsigned NOT NULL default '0', `PixelDiff` tinyint(3) unsigned NOT NULL default '0', `AlarmPixels` int(10) unsigned NOT NULL default '0', @@ -658,11 +684,13 @@ CREATE TABLE `Users` ( `Monitors` enum('None','View','Edit') NOT NULL default 'None', `Groups` enum('None','View','Edit') NOT NULL default 'None', `Devices` enum('None','View','Edit') NOT NULL default 'None', + `Snapshots` enum('None','View','Edit') NOT NULL default 'None', `System` enum('None','View','Edit') NOT NULL default 'None', `MaxBandwidth` varchar(16), `MonitorIds` text, `TokenMinExpiry` BIGINT UNSIGNED NOT NULL DEFAULT 0, `APIEnabled` tinyint(3) UNSIGNED NOT NULL default 1, + `HomeView` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`Id`), UNIQUE KEY `UC_Username` (`Username`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -703,6 +731,7 @@ DROP TABLE IF EXISTS `Zones`; CREATE TABLE `Zones` ( `Id` int(10) unsigned NOT NULL auto_increment, `MonitorId` int(10) unsigned NOT NULL default '0', + FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE, `Name` varchar(64) NOT NULL default '', `Type` enum('Active','Inclusive','Exclusive','Preclusive','Inactive','Privacy') NOT NULL default 'Active', `Units` enum('Pixels','Percent') NOT NULL default 'Pixels', @@ -764,7 +793,42 @@ insert into Storage VALUES (NULL, '@ZM_DIR_EVENTS@', 'Default', 'local', NULL, N -- -- Create a default admin user. -- -insert into Users VALUES (NULL,'admin','$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6','',1,'View','Edit','Edit','Edit','Edit','Edit','Edit','','',0,1); +INSERT INTO `Users` ( + `Username`, + `Password`, + `Language`, + `Enabled`, + `Stream`, + `Events`, + `Control`, + `Monitors`, + `Groups`, + `Devices`, + `Snapshots`, + `System`, + `MaxBandwidth`, + `MonitorIds`, + `TokenMinExpiry`, + `APIEnabled`, + `HomeView` + ) VALUES ( + 'admin', + '$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC.ZK5eqbF.ZWfwH9bqUJ6', + '' /* Language */, + 1 /* Enabled */, + 'View' /* Stream */, + 'Edit' /* Events */, + 'Edit' /* Control */, + 'Edit' /* Monitors */, + 'Edit' /* Groups */, + 'Edit' /* Devices */, + 'Edit' /* Snapshots */, + 'Edit' /* System */, + '' /* Max Bandwidth */, + '' /* MonitorIds */, + 0 /* TokenMinExpiry */, + 0 /* Api Endabled */, + '' /* Homeview */); -- -- Add a sample filter to purge the oldest 100 events when the disk is 95% full @@ -797,7 +861,7 @@ INSERT INTO `Filters` VALUES ( 'PurgeWhenFull', - '{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}', + '{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}],"limit":100,"sort_asc":1}', 0/*AutoArchive*/, 0/*AutoVideo*/, 0/*AutoUpload*/, @@ -841,7 +905,7 @@ INSERT INTO `Filters` ) VALUES ( 'Update DiskSpace', - '{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}', + '{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}]}', 0/*AutoArchive*/, 0/*AutoVideo*/, 0/*AutoUpload*/, @@ -863,24 +927,24 @@ VALUES ( -- -- Add in some sample control protocol definitions -- -INSERT INTO Controls VALUES (NULL,'Pelco-D','Local','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-P','Local','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Sony VISCA','Local','Visca',1,1,0,0,1,0,0,0,1,0,16384,10,4000,1,1,6,1,1,1,0,1,0,1536,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,3,1,1,1,1,0,1,1,0,1,-15578,15578,100,10000,1,1,50,1,254,1,-7789,7789,100,5000,1,1,50,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Axis API v2','Remote','AxisV2',0,0,0,0,1,0,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,1,1,1,1,1,0,1,0,1,-360,360,1,90,0,NULL,NULL,0,NULL,1,-360,360,1,90,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Panasonic IP','Remote','PanasonicIP',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Neu-Fusion NCS370','Remote','Ncs370',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,24,1,0,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'AirLink SkyIPCam 7xx','Remote','SkyIPCam7xx',0,0,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,1,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-D','Ffmpeg','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-P','Ffmpeg','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8620','Ffmpeg','FI8620_Y2k',0,0,0,0,1,0,0,0,1,1,10,1,10,1,1,63,1,1,0,0,1,1,63,1,63,1,1,63,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,1,360,1,360,1,1,63,0,0,1,1,90,1,90,1,1,63,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8608W','Ffmpeg','FI8608W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,128,0,0,1,0,0,0,0,1,1,128,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8908W','Remote','FI8908W',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI9821W','Ffmpeg','FI9821W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,100,1,1,0,0,1,0,100,0,100,1,0,100,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,100,0,100,1,0,100,1,16,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Loftek Sentinel','Remote','LoftekSentinel',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Toshiba IK-WB11A','Remote','Toshiba_IK_WB11A',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'WanscamPT','Remote','Wanscam',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'3S Domo N5071', 'Remote', '3S', 0, 0, 1, 0,1, 0, 1, 1, 0, 0, 9999, 0, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 20, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 1, 9999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 1, 0, 1, 1, 0, 0, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 0, 0); -INSERT INTO Controls VALUES (NULL,'ONVIF Camera','Ffmpeg','onvif',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Pelco-D','Local','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO `Controls` VALUES (NULL,'Pelco-P','Local','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO `Controls` VALUES (NULL,'Sony VISCA','Local','Visca',1,1,0,0,1,0,0,0,1,0,16384,10,4000,1,1,6,1,1,1,0,1,0,1536,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,3,1,1,1,1,0,1,1,0,1,-15578,15578,100,10000,1,1,50,1,254,1,-7789,7789,100,5000,1,1,50,1,254,0,0); +INSERT INTO `Controls` VALUES (NULL,'Axis API v2','Remote','AxisV2',0,0,0,0,1,0,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,1,1,1,1,1,0,1,0,1,-360,360,1,90,0,NULL,NULL,0,NULL,1,-360,360,1,90,0,NULL,NULL,0,NULL,0,0); +INSERT INTO `Controls` VALUES (NULL,'Panasonic IP','Remote','PanasonicIP',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO `Controls` VALUES (NULL,'Neu-Fusion NCS370','Remote','Ncs370',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,24,1,0,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO `Controls` VALUES (NULL,'AirLink SkyIPCam 7xx','Remote','SkyIPCam7xx',0,0,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,1,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO `Controls` VALUES (NULL,'Pelco-D','Ffmpeg','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO `Controls` VALUES (NULL,'Pelco-P','Ffmpeg','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam FI8620','Ffmpeg','FI8620_Y2k',0,0,0,0,1,0,0,0,1,1,10,1,10,1,1,63,1,1,0,0,1,1,63,1,63,1,1,63,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,1,360,1,360,1,1,63,0,0,1,1,90,1,90,1,1,63,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam FI8608W','Ffmpeg','FI8608W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,128,0,0,1,0,0,0,0,1,1,128,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam FI8908W','Remote','FI8908W',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam FI9821W','Ffmpeg','FI9821W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,100,1,1,0,0,1,0,100,0,100,1,0,100,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,100,0,100,1,0,100,1,16,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Loftek Sentinel','Remote','LoftekSentinel',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Toshiba IK-WB11A','Remote','Toshiba_IK_WB11A',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'WanscamPT','Remote','Wanscam',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'3S Domo N5071', 'Remote', '3S', 0, 0, 1, 0,1, 0, 1, 1, 0, 0, 9999, 0, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 20, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 1, 9999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 1, 0, 1, 1, 0, 0, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'ONVIF Camera','Ffmpeg','onvif',0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Foscam 9831W','Ffmpeg','FI9831W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Foscam FI8918W','Ffmpeg','FI8918W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','SPP1802SWPTZ',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,64,0,0,1,0,0,0,0,1,0,64,0,0,0,0); @@ -1024,6 +1088,33 @@ INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{ INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"24.5%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"19%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +CREATE TABLE Sessions ( + id char(32) not null, + access INT(10) UNSIGNED DEFAULT NULL, + data text, + PRIMARY KEY(id) +) ENGINE=InnoDB; + +CREATE TABLE Snapshots ( + `Id` int(10) unsigned NOT NULL auto_increment, + `Name` VARCHAR(64), + `Description` TEXT, + `CreatedBy` int(10), + `CreatedOn` datetime default NULL, + PRIMARY KEY(Id) +) ENGINE=InnoDB; + +CREATE TABLE Snapshot_Events ( + `Id` int(10) unsigned NOT NULL auto_increment, + `SnapshotId` int(10) unsigned NOT NULL, + FOREIGN KEY (`SnapshotId`) REFERENCES `Snapshots` (`Id`) ON DELETE CASCADE, + `EventId` bigint unsigned NOT NULL, + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, + KEY `Snapshot_Events_SnapshotId_idx` (`SnapshotId`), + PRIMARY KEY(Id) +) ENGINE=InnoDB; + + -- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts. source @PKGDATADIR@/db/triggers.sql -- diff --git a/db/zm_update-1.31.13.sql b/db/zm_update-1.31.13.sql index dd63b347a..150264027 100644 --- a/db/zm_update-1.31.13.sql +++ b/db/zm_update-1.31.13.sql @@ -13,6 +13,8 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; +UPDATE `Events` SET `SaveJPEGs`=(SELECT `SaveJPEGs` FROM `Monitors` WHERE Monitors.Id = MonitorId) WHERE `SaveJPEGs` IS NULL; + SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() AND table_name = 'Storage' diff --git a/db/zm_update-1.31.38.sql b/db/zm_update-1.31.38.sql index 0ca0be6ea..12fd3e96d 100644 --- a/db/zm_update-1.31.38.sql +++ b/db/zm_update-1.31.38.sql @@ -176,8 +176,10 @@ BEGIN WHERE Id=OLD.MonitorId; END IF; END IF; - ELSEIF ( NEW.Archived AND diff ) THEN - UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + ELSE + IF ( NEW.Archived AND diff ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + END IF; END IF; IF ( diff ) THEN @@ -185,7 +187,6 @@ BEGIN END IF; END; - // delimiter ; diff --git a/db/zm_update-1.31.40.sql b/db/zm_update-1.31.40.sql index 79fc3836c..50ffed736 100644 --- a/db/zm_update-1.31.40.sql +++ b/db/zm_update-1.31.40.sql @@ -10,3 +10,18 @@ SET @s = (SELECT IF( PREPARE stmt FROM @s; EXECUTE stmt; + + +ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Encoder' + ) > 0, +"SELECT 'Column Encoder already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Encoder` enum('auto','h264','h264_omx','mjpeg','mpeg1','mpeg2') AFTER `OutputCodec`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.31.5.sql b/db/zm_update-1.31.5.sql index 859487e88..d315c8e84 100644 --- a/db/zm_update-1.31.5.sql +++ b/db/zm_update-1.31.5.sql @@ -10,7 +10,7 @@ SET @s = (SELECT IF( AND column_name = 'ParentId' ) > 0, "SELECT 'Column GroupId exists in Groups'", -"ALTER TABLE Groups ADD `ParentId` int(10) unsigned AFTER `Name`" +"ALTER TABLE `Groups` ADD `ParentId` int(10) unsigned AFTER `Name`" )); PREPARE stmt FROM @s; diff --git a/db/zm_update-1.31.7.sql b/db/zm_update-1.31.7.sql index 1221d9adb..0afd76ce5 100644 --- a/db/zm_update-1.31.7.sql +++ b/db/zm_update-1.31.7.sql @@ -3,7 +3,7 @@ SET @s = (SELECT IF( AND table_name = 'Groups' AND column_name = 'MonitorIds' ) > 0, - "ALTER TABLE Groups MODIFY `MonitorIds` text NOT NULL", + "ALTER TABLE `Groups` MODIFY `MonitorIds` text NOT NULL", "SELECT 'Groups no longer has MonitorIds'" )); diff --git a/db/zm_update-1.35.10.sql b/db/zm_update-1.35.10.sql index 341c5e162..81c768b57 100644 --- a/db/zm_update-1.35.10.sql +++ b/db/zm_update-1.35.10.sql @@ -1,46 +1,14 @@ +-- +-- Add AutoUnarchive action to Filters +-- + SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ManufacturerId' + AND table_name = 'Filters' + AND column_name = 'AutoUnarchive' ) > 0, -"SELECT 'Column ManufacturerId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ManufacturerId' - ) > 0, -"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ModelId' - ) > 0, -"SELECT 'Column ModelId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" -)); - -PREPARE stmt FROM @s; -EXECUTE stmt; - -SET @s = (SELECT IF( - (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() - AND table_name = 'Monitors' - AND column_name = 'ModelId' - ) > 0, -"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", -"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +"SELECT 'Column AutoUunarchive already exists in Filters'", +"ALTER TABLE Filters ADD `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoArchive`" )); PREPARE stmt FROM @s; diff --git a/db/zm_update-1.35.11.sql b/db/zm_update-1.35.11.sql new file mode 100644 index 000000000..5e4338d76 --- /dev/null +++ b/db/zm_update-1.35.11.sql @@ -0,0 +1,92 @@ +/* Change Id type to BIGINT. */ +set @exist := (SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Events' AND COLUMN_NAME = 'Id' and DATA_TYPE='bigint'); + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Events MODIFY Id bigint unsigned NOT NULL auto_increment", "SELECT 'Events.Id is already BIGINT'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Frames' and column_name='EventId' and referenced_table_name='Events' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY EventId in Frames already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for EventId to Frames'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Frames'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Frames WHERE EventId NOT IN (SELECT Id FROM Events)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE Frames ADD FOREIGN KEY (EventId) REFERENCES Events (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='EventId' and referenced_table_name='Events' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY EventId in Stats already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding FOREIGN KEY for EventId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE EventId NOT IN (SELECT Id FROM Events);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (EventId) REFERENCES Events (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for MonitorId in Stats already exists'", @sql_stmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding FOREIGN KEY for MonitorId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE MonitorId NOT IN (SELECT Id FROM Monitors);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (MonitorId) REFERENCES Monitors (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Stats' and column_name='ZoneId' and referenced_table_name='Zones' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for ZoneId in Stats already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for ZoneId to Stats'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Stats'", "SELECT 'Ok'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM Stats WHERE ZoneId NOT IN (SELECT Id FROM Zones);", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE Stats ADD FOREIGN KEY (ZoneId) REFERENCES Zones (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +SELECT 'Adding foreign key for MonitorId to Zones'; +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Zones' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY for MonitorId in Zones already exists'", @sqlstmnt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for MonitorId in Zones'", @sqlstmnt); + +/*"SELECT 'FOREIGN KEY for MonitorId in Zones does not already exist'");*/ +set @badzones := (select count(*) FROM Zones WHERE MonitorId NOT IN (SELECT Id FROM Monitors)); +set @sqlstmt := if ( @badzones > 0, "SELECT 'You have Zones with no Monitor record in the Monitors table. Please delete them manually'", "ALTER TABLE Zones ADD FOREIGN KEY (MonitorId) REFERENCES Monitors (Id)"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; diff --git a/db/zm_update-1.35.12.sql b/db/zm_update-1.35.12.sql new file mode 100644 index 000000000..b8c54f713 --- /dev/null +++ b/db/zm_update-1.35.12.sql @@ -0,0 +1,18 @@ +-- +-- Update Filters table to have a LockRows Column +-- + +SELECT 'Checking for LockRows in Filters'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Filters' + AND table_schema = DATABASE() + AND column_name = 'LockRows' + ) > 0, +"SELECT 'Column LockRows already exists in Filters'", +"ALTER TABLE Filters ADD COLUMN `LockRows` tinyint(1) unsigned NOT NULL default '0' AFTER `Concurrent`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.13.sql b/db/zm_update-1.35.13.sql new file mode 100644 index 000000000..2911aaf65 --- /dev/null +++ b/db/zm_update-1.35.13.sql @@ -0,0 +1,101 @@ +/* DateTime is invalid and it being set here will cause warnings because it isn't in the dropdown set of values in Filter edit. */ +UPDATE Config SET Value='StartDateTime' WHERE Name='ZM_WEB_EVENT_SORT_FIELD' AND Value='DateTime'; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'StartDateTime' + ) > 0, +"SELECT 'Column StartDateTime already exists in Events'", +"ALTER TABLE Events CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'EndDateTime' + ) > 0, + "SELECT 'Column EndDateTime already exists in Events'", + "ALTER TABLE Events CHANGE EndTime EndDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Hour' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Hour'", + "ALTER TABLE Events_Hour CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Day' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Day'", + "ALTER TABLE Events_Day CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Week' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Week'", + "ALTER TABLE Events_Week CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events_Month' + AND column_name = 'StartDateTime' + ) > 0, + "SELECT 'Column StartDateTime already exists in Events_Month'", + "ALTER TABLE Events_Month CHANGE StartTime StartDateTime datetime default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +delimiter // + +DROP TRIGGER IF EXISTS event_insert_trigger// + +/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count. + * The DiskSpace will get update in the Event Update Trigger + */ +CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events +FOR EACH ROW + BEGIN + + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + UPDATE Monitors SET + HourEvents = COALESCE(HourEvents,0)+1, + DayEvents = COALESCE(DayEvents,0)+1, + WeekEvents = COALESCE(WeekEvents,0)+1, + MonthEvents = COALESCE(MonthEvents,0)+1, + TotalEvents = COALESCE(TotalEvents,0)+1 + WHERE Id=NEW.MonitorId; +END; +// + +delimiter ; diff --git a/db/zm_update-1.35.14.sql b/db/zm_update-1.35.14.sql new file mode 100644 index 000000000..daa8239ff --- /dev/null +++ b/db/zm_update-1.35.14.sql @@ -0,0 +1,574 @@ +SELECT 'This update may make changes that require SUPER privileges. If you see an error message saying: + +ERROR 1419 (HY000) at line 298: You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) + +You will have to either run this update as root manually using something like (on ubuntu/debian) + +sudo mysql --defaults-file=/etc/mysql/debian.cnf zm < /usr/share/zoneminder/db/zm_update-1.35.14.sql + +OR + +sudo mysql --defaults-file=/etc/mysql/debian.cnf "set global log_bin_trust_function_creators=1;" +sudo zmupdate.pl +'; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEvents' + ) > 0, +"SELECT 'Column TotalEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `TotalEvents` INT(10) AFTER `CaptureBandwidth`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'TotalEvents' + ) > 0, +"SELECT 'Column TotalEvents is already removed from Monitors'", +"ALTER TABLE `Monitors` DROP `TotalEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEventDiskSpace' + ) > 0, +"SELECT 'Column TotalEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `TotalEventDiskSpace` BIGINT AFTER `TotalEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'TotalEventDiskSpace' + ) > 0, +"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'", +"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEvents' + ) > 0, +"SELECT 'Column HourEvents already exists in Monitors'", +"ALTER TABLE `Monitor_Status` ADD `HourEvents` INT(10) AFTER `TotalEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'HourEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `HourEvents`", +"SELECT 'Column HourEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEventDiskSpace' + ) > 0, +"SELECT 'Column HourEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `HourEventDiskSpace` BIGINT AFTER `HourEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'HourEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `HourEventDiskSpace`", +"SELECT 'Column HourEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEvents' + ) > 0, +"SELECT 'Column DayEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `DayEvents` INT(10) AFTER `HourEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DayEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `DayEvents`", +"SELECT 'Column DayEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEventDiskSpace' + ) > 0, +"SELECT 'Column DayEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `DayEventDiskSpace` BIGINT AFTER `DayEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DayEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `DayEventDiskSpace`", +"SELECT 'Column DayEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEvents' + ) > 0, +"SELECT 'Column WeekEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `WeekEvents` INT(10) AFTER `DayEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'WeekEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `WeekEvents`", +"SELECT 'Column WeekEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"SELECT 'Column WeekEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `WeekEventDiskSpace` BIGINT AFTER `WeekEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `WeekEventDiskSpace`", +"SELECT 'Column WeekEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEvents' + ) > 0, +"SELECT 'Column MonthEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `MonthEvents` INT(10) AFTER `WeekEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'MonthEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `MonthEvents`", +"SELECT 'Column MonthEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"SELECT 'Column MonthEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `MonthEventDiskSpace` BIGINT AFTER `MonthEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `MonthEventDiskSpace`", +"SELECT 'Column MonthEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEvents' + ) > 0, +"SELECT 'Column ArchivedEvents already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `ArchivedEvents` INT(10) AFTER `MonthEventDiskSpace`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ArchivedEvents' + ) > 0, +"ALTER TABLE `Monitors` DROP `ArchivedEvents`", +"SELECT 'Column ArchivedEvents is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"SELECT 'Column ArchivedEventDiskSpace already exists in Monitor_Status'", +"ALTER TABLE `Monitor_Status` ADD `ArchivedEventDiskSpace` BIGINT AFTER `ArchivedEvents`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitors` DROP `ArchivedEventDiskSpace`", +"SELECT 'Column ArchivedEventDiskSpace is already removed from Monitors'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Monitor_Status INNER JOIN ( + SELECT MonitorId, + COUNT(Id) AS TotalEvents, + SUM(DiskSpace) AS TotalEventDiskSpace, + SUM(IF(Archived,1,0)) AS ArchivedEvents, + SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace + FROM Events GROUP BY MonitorId + ) AS E ON E.MonitorId=Monitor_Status.MonitorId SET + Monitor_Status.TotalEvents = E.TotalEvents, + Monitor_Status.TotalEventDiskSpace = E.TotalEventDiskSpace, + Monitor_Status.ArchivedEvents = E.ArchivedEvents, + Monitor_Status.ArchivedEventDiskSpace = E.ArchivedEventDiskSpace, + Monitor_Status.HourEvents = E.HourEvents, + Monitor_Status.HourEventDiskSpace = E.HourEventDiskSpace, + Monitor_Status.DayEvents = E.DayEvents, + Monitor_Status.DayEventDiskSpace = E.DayEventDiskSpace, + Monitor_Status.WeekEvents = E.WeekEvents, + Monitor_Status.WeekEventDiskSpace = E.WeekEventDiskSpace, + Monitor_Status.MonthEvents = E.MonthEvents, + Monitor_Status.MonthEventDiskSpace = E.MonthEventDiskSpace; + + +delimiter // +DROP TRIGGER IF EXISTS Events_Hour_delete_trigger// +CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + HourEvents = GREATEST(COALESCE(HourEvents,1)-1,0), + HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Hour_update_trigger// + +CREATE TRIGGER Events_Hour_update_trigger AFTER UPDATE ON Events_Hour +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; +// + +DROP TRIGGER IF EXISTS Events_Day_delete_trigger// +CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + DayEvents = GREATEST(COALESCE(DayEvents,1)-1,0), + DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Day_update_trigger; +CREATE TRIGGER Events_Day_update_trigger AFTER UPDATE ON Events_Day +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + + +DROP TRIGGER IF EXISTS Events_Week_delete_trigger// +CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + WeekEvents = GREATEST(COALESCE(WeekEvents,1)-1,0), + WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Week_update_trigger; +CREATE TRIGGER Events_Week_update_trigger AFTER UPDATE ON Events_Week +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + +DROP TRIGGER IF EXISTS Events_Month_delete_trigger// +CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month +FOR EACH ROW BEGIN + UPDATE Monitor_Status SET + MonthEvents = GREATEST(COALESCE(MonthEvents,1)-1,0), + MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS Events_Month_update_trigger; +CREATE TRIGGER Events_Month_update_trigger AFTER UPDATE ON Events_Month +FOR EACH ROW + BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( diff ) THEN + IF ( NEW.MonitorID != OLD.MonitorID ) THEN + UPDATE Monitor_Status SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace),0) WHERE Monitor_Status.MonitorId=OLD.MonitorId; + UPDATE Monitor_Status SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSE + UPDATE Monitor_Status SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + END IF; + END IF; + END; + // + +drop procedure if exists update_storage_stats// + +drop trigger if exists event_update_trigger// + +CREATE TRIGGER event_update_trigger AFTER UPDATE ON Events +FOR EACH ROW +BEGIN + declare diff BIGINT default 0; + + set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0); + IF ( NEW.StorageId = OLD.StorageID ) THEN + IF ( diff ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) + diff,0) WHERE Storage.Id = OLD.StorageId; + END IF; + ELSE + IF ( NEW.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Storage.Id = NEW.StorageId; + END IF; + IF ( OLD.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - OLD.DiskSpace,0) WHERE Storage.Id = OLD.StorageId; + END IF; + END IF; + + UPDATE Events_Hour SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Day SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Week SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Events_Month SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + + IF ( NEW.Archived != OLD.Archived ) THEN + IF ( NEW.Archived ) THEN + INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace); + UPDATE Monitor_Status SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + ELSEIF ( OLD.Archived ) THEN + DELETE FROM Events_Archived WHERE EventId=OLD.Id; + UPDATE Monitor_Status + SET + ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,0)-1,0), + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + ELSE + IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + UPDATE Monitor_Status SET + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; + END IF; + ELSEIF ( NEW.Archived AND diff ) THEN + UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id; + END IF; + + IF ( diff ) THEN + UPDATE Monitor_Status + SET + TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; + +END; + +// + +DROP TRIGGER IF EXISTS event_insert_trigger// + +/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count. + * The DiskSpace will get update in the Event Update Trigger + */ +CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events +FOR EACH ROW + BEGIN + + INSERT INTO Events_Hour (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Day (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Week (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + INSERT INTO Events_Month (EventId,MonitorId,StartDateTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartDateTime,0); + UPDATE Monitor_Status SET + HourEvents = COALESCE(HourEvents,0)+1, + DayEvents = COALESCE(DayEvents,0)+1, + WeekEvents = COALESCE(WeekEvents,0)+1, + MonthEvents = COALESCE(MonthEvents,0)+1, + TotalEvents = COALESCE(TotalEvents,0)+1 + WHERE Monitor_Status.MonitorId=NEW.MonitorId; +END; +// + +DROP TRIGGER IF EXISTS event_delete_trigger// + +CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events +FOR EACH ROW +BEGIN + IF ( OLD.DiskSpace ) THEN + UPDATE Storage SET DiskSpace = GREATEST(COALESCE(DiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) WHERE Storage.Id = OLD.StorageId; + END IF; + DELETE FROM Events_Hour WHERE EventId=OLD.Id; + DELETE FROM Events_Day WHERE EventId=OLD.Id; + DELETE FROM Events_Week WHERE EventId=OLD.Id; + DELETE FROM Events_Month WHERE EventId=OLD.Id; + IF ( OLD.Archived ) THEN + DELETE FROM Events_Archived WHERE EventId=OLD.Id; + UPDATE Monitor_Status SET + ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,1) - 1,0), + ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0), + TotalEvents = GREATEST(COALESCE(TotalEvents,1) - 1,0), + TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + ELSE + UPDATE Monitor_Status SET + TotalEvents = GREATEST(COALESCE(TotalEvents,1)-1,0), + TotalEventDiskSpace=GREATEST(COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) + WHERE Monitor_Status.MonitorId=OLD.MonitorId; + END IF; +END; + +// + +DELIMITER ; + +REPLACE INTO Events_Hour SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour); +REPLACE INTO Events_Day SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day); +REPLACE INTO Events_Week SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week); +REPLACE INTO Events_Month SELECT Id,MonitorId,StartDateTime,DiskSpace FROM Events WHERE StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month); +REPLACE INTO Events_Archived SELECT Id,MonitorId,DiskSpace FROM Events WHERE Archived=1; + +UPDATE Monitor_Status INNER JOIN ( + SELECT MonitorId, + COUNT(Id) AS TotalEvents, + SUM(DiskSpace) AS TotalEventDiskSpace, + SUM(IF(Archived,1,0)) AS ArchivedEvents, + SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace + FROM Events GROUP BY MonitorId + ) AS E ON E.MonitorId=Monitor_Status.MonitorId SET + Monitor_Status.TotalEvents = E.TotalEvents, + Monitor_Status.TotalEventDiskSpace = E.TotalEventDiskSpace, + Monitor_Status.ArchivedEvents = E.ArchivedEvents, + Monitor_Status.ArchivedEventDiskSpace = E.ArchivedEventDiskSpace, + Monitor_Status.HourEvents = E.HourEvents, + Monitor_Status.HourEventDiskSpace = E.HourEventDiskSpace, + Monitor_Status.DayEvents = E.DayEvents, + Monitor_Status.DayEventDiskSpace = E.DayEventDiskSpace, + Monitor_Status.WeekEvents = E.WeekEvents, + Monitor_Status.WeekEventDiskSpace = E.WeekEventDiskSpace, + Monitor_Status.MonthEvents = E.MonthEvents, + Monitor_Status.MonthEventDiskSpace = E.MonthEventDiskSpace; diff --git a/db/zm_update-1.35.15.sql b/db/zm_update-1.35.15.sql new file mode 100644 index 000000000..65f4e5426 --- /dev/null +++ b/db/zm_update-1.35.15.sql @@ -0,0 +1,70 @@ +/* +Add the EndTime IS NOT NULL term to the Update Disk Space Filter. +This will only work if they havn't modified the stock filter + */ +UPDATE Filters SET Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}]}' WHERE Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}'; + +/* +Add the EndTime IS NOT NULL term to the Purge When Full Filter. +This will only work if they havn't modified the stock filter . +This is important to prevent SQL Errors inserting into Frames table if PurgeWhenFull deletes in-progress events. + */ +UPDATE Filters SET Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}],"limit":100,"sort_asc":1}' WHERE Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}'; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='GroupId' and referenced_table_name='Groups' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY GroupId in Groups_Monitors already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for GroupId to Groups_Monitors'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `GroupId` NOT IN (SELECT `Id` FROM `Groups`)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY MonitorId in Groups_Monitors already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for MonitorId to Groups_Monitors'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `MonitorId` NOT IN (SELECT `Id` FROM `Monitors`)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +/* Add FOREIGN KEYS After deleting lost records */ +set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups' and column_name='ParentId' and referenced_table_name='Groups' and referenced_column_name='Id'); + +set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'"); +set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY ParentId in Groups already exists'", @sqlstmt); +set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for ParentId to Groups'", @sqlstmt); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + +set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups'", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "UPDATE `Groups` SET `ParentId` = NULL WHERE (ParentId IS NOT NULL) and ParentId NOT IN (SELECT * FROM(SELECT Id FROM `Groups`)tblTmp)", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; +set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups` ADD FOREIGN KEY (ParentId) REFERENCES `Groups` (Id) ON DELETE CASCADE", "SELECT '.'"); +PREPARE stmt FROM @sqlstmt; +EXECUTE stmt; + diff --git a/db/zm_update-1.35.16.sql b/db/zm_update-1.35.16.sql new file mode 100644 index 000000000..50c952bca --- /dev/null +++ b/db/zm_update-1.35.16.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DecodingEnabled' + ) > 0, + "SELECT 'Column DecodingEnabled already exists in Monitors'", + "ALTER TABLE Monitors ADD `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1' AFTER `Enabled`" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.17.sql b/db/zm_update-1.35.17.sql new file mode 100644 index 000000000..ec4745b8a --- /dev/null +++ b/db/zm_update-1.35.17.sql @@ -0,0 +1,14 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Encoder' + ) > 0, +"SELECT 'Column Encoder already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Encoder` enum('auto','h264','libx264','h264_omx','h264_vaapi','mjpeg','mpeg1','mpeg2') AFTER `OutputCodec`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +ALTER TABLE `Monitors` MODIFY `Encoder` enum('auto','h264','libx264', 'h264_omx', 'h264_vaapi', 'mjpeg','mpeg1','mpeg2'); + +ALTER TABLE `Monitors` MODIFY `OutputCodec` INT UNSIGNED default 0; diff --git a/db/zm_update-1.35.18.sql b/db/zm_update-1.35.18.sql new file mode 100644 index 000000000..a46cb5895 --- /dev/null +++ b/db/zm_update-1.35.18.sql @@ -0,0 +1,11 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'RTSPServer' + ) > 0, +"SELECT 'Column RTSPServer already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE AFTER `Longitude`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.19.sql b/db/zm_update-1.35.19.sql new file mode 100644 index 000000000..8772491db --- /dev/null +++ b/db/zm_update-1.35.19.sql @@ -0,0 +1,11 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'RTSPStreamName' + ) > 0, +"SELECT 'Column RTSPStreamName already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RTSPStreamName` varchar(255) NOT NULL default '' AFTER `RTSPServer`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.20.sql b/db/zm_update-1.35.20.sql new file mode 100644 index 000000000..275515dbf --- /dev/null +++ b/db/zm_update-1.35.20.sql @@ -0,0 +1,15 @@ +-- +-- Add SecondPath to Monitors +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'SecondPath' + ) > 0, +"SELECT 'Column SecondPath already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `SecondPath` VARCHAR(255) AFTER `Path`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.21.sql b/db/zm_update-1.35.21.sql new file mode 100644 index 000000000..d3d80e08c --- /dev/null +++ b/db/zm_update-1.35.21.sql @@ -0,0 +1,56 @@ +-- +-- Add HomeView to Users +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Users' + AND column_name = 'HomeView' + ) > 0, +"SELECT 'Column HomeView already exists in Users'", +"ALTER TABLE `Users` ADD `HomeView` varchar(64) NOT NULL DEFAULT '' AFTER `APIEnabled`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Snapshots' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'Snapshots table exists'", + "CREATE TABLE Snapshots ( + `Id` int(10) unsigned NOT NULL auto_increment, + `Name` VARCHAR(64), + `Description` TEXT, + `CreatedBy` int(10), + `CreatedOn` datetime default NULL, + PRIMARY KEY(Id) +) ENGINE=InnoDB;" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Snapshot_Events' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'Snapshot_Events table exists'", + "CREATE TABLE Snapshot_Events ( + `Id` int(10) unsigned NOT NULL auto_increment, + `SnapshotId` int(10) unsigned NOT NULL, + FOREIGN KEY (`SnapshotId`) REFERENCES `Snapshots` (`Id`) ON DELETE CASCADE, + `EventId` bigint unsigned NOT NULL, + FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE, + KEY `Snapshot_Events_SnapshotId_idx` (`SnapshotId`), + PRIMARY KEY(Id) +) ENGINE=InnoDB;" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.22.sql b/db/zm_update-1.35.22.sql new file mode 100644 index 000000000..2e4fe3a93 --- /dev/null +++ b/db/zm_update-1.35.22.sql @@ -0,0 +1,19 @@ +-- +-- Add MaxImageBufferCount, set it to ImageBufferCount if that was large and set ImageBufferCount to 3 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'MaxImageBufferCount' + ) > 0, +"SELECT 'Column MaxImageBufferCount already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `MaxImageBufferCount` smallint(5) unsigned NOT NULL default '0' AFTER `ImageBufferCount`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE `Monitors` SET MaxImageBufferCount=ImageBufferCount WHERE ImageBufferCount >= 20; +UPDATE `Monitors` SET ImageBufferCount = 3; + diff --git a/db/zm_update-1.35.23.sql b/db/zm_update-1.35.23.sql new file mode 100644 index 000000000..22172a707 --- /dev/null +++ b/db/zm_update-1.35.23.sql @@ -0,0 +1,2 @@ + +UPDATE `Controls` SET `Protocol`='FoscamCGI' where `Protocol`='onvif'; diff --git a/db/zm_update-1.35.24.sql.in b/db/zm_update-1.35.24.sql.in new file mode 100644 index 000000000..5dc117cab --- /dev/null +++ b/db/zm_update-1.35.24.sql.in @@ -0,0 +1,69 @@ +SELECT 'This update may make changes that require SUPER privileges. If you see an error message saying: + +ERROR 1419 (HY000) at line 298: You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) + +You will have to either run this update as root manually using something like (on ubuntu/debian) + +sudo mysql --defaults-file=/etc/mysql/debian.cnf zm < /usr/share/zoneminder/db/zm_update-1.35.24.sql + +OR + +sudo mysql --defaults-file=/etc/mysql/debian.cnf "set global log_bin_trust_function_creators=1;" +sudo zmupdate.pl + +OR + +Turn off binary logging in your mysql server by adding this to your mysql config. +[mysqld] +skip-log-bin + +'; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Event_Summaries' + ) > 0 + , + "SELECT 'Event_Summaries Already exists'", + " + CREATE TABLE `Event_Summaries` ( + `MonitorId` int(10) unsigned NOT NULL, + `TotalEvents` int(10) default NULL, + `TotalEventDiskSpace` bigint default NULL, + `HourEvents` int(10) default NULL, + `HourEventDiskSpace` bigint default NULL, + `DayEvents` int(10) default NULL, + `DayEventDiskSpace` bigint default NULL, + `WeekEvents` int(10) default NULL, + `WeekEventDiskSpace` bigint default NULL, + `MonthEvents` int(10) default NULL, + `MonthEventDiskSpace` bigint default NULL, + `ArchivedEvents` int(10) default NULL, + `ArchivedEventDiskSpace` bigint default NULL, + PRIMARY KEY (`MonitorId`) +) ENGINE=InnoDb; +" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +DELETE FROM Event_Summaries; + +REPLACE INTO Event_Summaries + SELECT MonitorId, + COUNT(Id) AS TotalEvents, + SUM(DiskSpace) AS TotalEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents, + SUM(IF(StartDateTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace, + SUM(IF(Archived,1,0)) AS ArchivedEvents, + SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace + FROM Events GROUP BY MonitorId; + +source @PKGDATADIR@/db/triggers.sql diff --git a/db/zm_update-1.35.25.sql b/db/zm_update-1.35.25.sql new file mode 100644 index 000000000..387ef09b0 --- /dev/null +++ b/db/zm_update-1.35.25.sql @@ -0,0 +1,144 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `TotalEvents`", +"SELECT 'Column TotalEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'TotalEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `TotalEventDiskSpace`", +"SELECT 'Column TotalEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `HourEvents`", +"SELECT 'Column HourEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'HourEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `HourEventDiskSpace`", +"SELECT 'Column HourEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `DayEvents`", +"SELECT 'Column DayEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'DayEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `DayEventDiskSpace`", +"SELECT 'Column DayEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `WeekEvents`", +"SELECT 'Column WeekEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'WeekEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `WeekEventDiskSpace`", +"SELECT 'Column WeekEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `MonthEvents`", +"SELECT 'Column MonthEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'MonthEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `MonthEventDiskSpace`", +"SELECT 'Column MonthEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEvents' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `ArchivedEvents`", +"SELECT 'Column ArchivedEvents already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitor_Status' + AND column_name = 'ArchivedEventDiskSpace' + ) > 0, +"ALTER TABLE `Monitor_Status` DROP `ArchivedEventDiskSpace`", +"SELECT 'Column ArchivedEventDiskSpace already removed from Monitor_Status'" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Importance' + ) > 0, +"SELECT 'Column Importance already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Importance` enum('Not','Less','Normal') AFTER `RTSPStreamName`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/db/zm_update-1.35.26.sql b/db/zm_update-1.35.26.sql new file mode 100644 index 000000000..123f8b56a --- /dev/null +++ b/db/zm_update-1.35.26.sql @@ -0,0 +1,17 @@ +-- +-- Add Snapshot permission to Users +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Users' + AND column_name = 'Snapshots' + ) > 0, +"SELECT 'Column Snapshots already exists in Users'", +"ALTER TABLE `Users` ADD `Snapshots` enum('None','View','Edit') NOT NULL default 'None' AFTER `Devices`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE `Users` SET `Snapshots` = `Events`; diff --git a/db/zm_update-1.35.27.sql b/db/zm_update-1.35.27.sql new file mode 100644 index 000000000..8efef50e5 --- /dev/null +++ b/db/zm_update-1.35.27.sql @@ -0,0 +1,15 @@ +-- +-- Add ModectDuringPTZ +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModectDuringPTZ' + ) > 0, +"SELECT 'Column ModectDuringPTZ already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModectDuringPTZ` tinyint(3) unsigned NOT NULL default '0' AFTER `ReturnDelay`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.28.sql b/db/zm_update-1.35.28.sql new file mode 100644 index 000000000..a281507dc --- /dev/null +++ b/db/zm_update-1.35.28.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Encoder` varchar(32); diff --git a/db/zm_update-1.35.29.sql b/db/zm_update-1.35.29.sql new file mode 100644 index 000000000..9030e5863 --- /dev/null +++ b/db/zm_update-1.35.29.sql @@ -0,0 +1,61 @@ +<<<<<<< HEAD +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'Column ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ManufacturerId` int(10) unsigned AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ManufacturerId' + ) > 0, +"SELECT 'FOREIGN KEY for ManufacturerId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ManufacturerId`) REFERENCES `Manufacturers` (Id)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'Column ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `ModelId` int(10) unsigned AFTER `ManufacturerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'ModelId' + ) > 0, +"SELECT 'FOREIGN KEY for ModelId already exists in Monitors'", +"ALTER TABLE `Monitors` ADD FOREIGN KEY (`ModelId`) REFERENCES `Models` (Id)" +======= +-- +-- Add AutoUnarchive action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoUnarchive' + ) > 0, +"SELECT 'Column AutoUunarchive already exists in Filters'", +"ALTER TABLE Filters ADD `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoArchive`" +>>>>>>> master +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.7.sql b/db/zm_update-1.35.7.sql new file mode 100644 index 000000000..84c3dbb1e --- /dev/null +++ b/db/zm_update-1.35.7.sql @@ -0,0 +1,23 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Latitude' + ) > 0, +"SELECT 'Column Latitude already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Latitude` DECIMAL(10,8) AFTER `Refresh`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Longitude' + ) > 0, +"SELECT 'Column Longitude already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `Longitude` DECIMAL(10,8) AFTER `Latitude`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.35.8.sql b/db/zm_update-1.35.8.sql new file mode 100644 index 000000000..1549903ab --- /dev/null +++ b/db/zm_update-1.35.8.sql @@ -0,0 +1,12 @@ +/* The MEMORY TABLE TYPE IS BAD! Switch to regular InnoDB */ + +DROP TABLE IF EXISTS `Monitor_Status`; +CREATE TABLE `Monitor_Status` ( + `MonitorId` int(10) unsigned NOT NULL, + `Status` enum('Unknown','NotRunning','Running','Connected','Signal') NOT NULL default 'Unknown', + `CaptureFPS` DECIMAL(10,2) NOT NULL default 0, + `AnalysisFPS` DECIMAL(5,2) NOT NULL default 0, + `CaptureBandwidth` INT NOT NULL default 0, + PRIMARY KEY (`MonitorId`) +) ENGINE=InnoDB; + diff --git a/db/zm_update-1.35.9.sql b/db/zm_update-1.35.9.sql new file mode 100644 index 000000000..b5f807225 --- /dev/null +++ b/db/zm_update-1.35.9.sql @@ -0,0 +1,21 @@ +-- +-- This adds Sessions Table +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Sessions' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'Sessions table exists'", + "CREATE TABLE Sessions ( + id char(32) not null, + access INT(10) UNSIGNED DEFAULT NULL, + data text, + PRIMARY KEY(id) +) ENGINE=InnoDB;" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt new file mode 100644 index 000000000..792048adf --- /dev/null +++ b/dep/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(jwt-cpp) +add_subdirectory(libbcrypt) +add_subdirectory(RtspServer) +add_subdirectory(span-lite) diff --git a/dep/RtspServer b/dep/RtspServer new file mode 160000 index 000000000..cd7fd49be --- /dev/null +++ b/dep/RtspServer @@ -0,0 +1 @@ +Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878 diff --git a/dep/jwt-cpp/CMakeLists.txt b/dep/jwt-cpp/CMakeLists.txt new file mode 100644 index 000000000..81ddc84a1 --- /dev/null +++ b/dep/jwt-cpp/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(jwt-cpp INTERFACE) +add_library(jwt-cpp::jwt-cpp ALIAS jwt-cpp) + +target_include_directories(jwt-cpp + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/src/jwt-cpp/Doxyfile b/dep/jwt-cpp/Doxyfile similarity index 99% rename from src/jwt-cpp/Doxyfile rename to dep/jwt-cpp/Doxyfile index e3303d2b9..0e912fd79 100644 --- a/src/jwt-cpp/Doxyfile +++ b/dep/jwt-cpp/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "JWT-C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = 0.5.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -889,7 +889,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = *nlohmann*, *picojson* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -900,13 +900,13 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = jwt::details # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = example # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1666,7 +1666,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of diff --git a/src/jwt-cpp/LICENSE b/dep/jwt-cpp/LICENSE similarity index 100% rename from src/jwt-cpp/LICENSE rename to dep/jwt-cpp/LICENSE diff --git a/dep/jwt-cpp/README.md b/dep/jwt-cpp/README.md new file mode 100644 index 000000000..5e3903262 --- /dev/null +++ b/dep/jwt-cpp/README.md @@ -0,0 +1,208 @@ +# ![logo](https://raw.githubusercontent.com/Thalhammer/jwt-cpp/master/.github/logo.svg) + +[![License Badge](https://img.shields.io/github/license/Thalhammer/jwt-cpp)](https://github.com/Thalhammer/jwt-cpp/blob/master/LICENSE) +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings) +[![Linux Badge][Linux]][Cross-Platform] +[![MacOS Badge][MacOS]][Cross-Platform] +[![Windows Badge][Windows]][Cross-Platform] +[![Coverage Status](https://coveralls.io/repos/github/Thalhammer/jwt-cpp/badge.svg?branch=master)](https://coveralls.io/github/Thalhammer/jwt-cpp?branch=master) +[![Documentation Badge](https://img.shields.io/badge/Documentation-master-blue)](https://thalhammer.github.io/jwt-cpp/) +[![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/Thalhammer/jwt-cpp?include_prereleases)](https://github.com/Thalhammer/jwt-cpp/releases) +[![Stars Badge](https://img.shields.io/github/stars/Thalhammer/jwt-cpp)](https://github.com/Thalhammer/jwt-cpp/stargazers) + +[Linux]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/ubuntu-latest/shields.json +[MacOS]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/macos-latest/shields.json +[Windows]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/cross-platform/windows-latest/shields.json +[Cross-Platform]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Cross-Platform+CI%22 + +A header only library for creating and validating [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) in C++11. For a great introduction, [read this](https://jwt.io/introduction/). + +## Signature algorithms + +jwt-cpp supports all the algorithms defined by the specifications. The modular design allows to easily add additional algorithms without any problems. If you need any feel free to create a pull request or [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new). + +For completeness, here is a list of all supported algorithms: + +| HMSC | RSA | ECDSA | PSS | EdDSA | +| ----- | ----- | ----- | ----- | ------- | +| HS256 | RS256 | ES256 | PS256 | Ed25519 | +| HS384 | RS384 | ES384 | PS384 | Ed448 | +| HS512 | RS512 | ES512 | PS512 | | + +## SSL Compatibility + +In the name of flexibility and extensibility, jwt-cpp supports both [OpenSSL](https://github.com/openssl/openssl) and [LibreSSL](https://github.com/libressl-portable/portable). These are the version which are, or have been, tested: + +| OpenSSL | LibreSSL | +| -------------- | --------------- | +| [1.0.2][1.0.2] | ![3.1.5][3.1] | +| 1.1.0 | ![3.2.3][3.2] | +| [1.1.1][1.1.1] | ![3.3.1][3.3] | + +[1.0.2]: https://travis-ci.com/github/Thalhammer/jwt-cpp +[1.1.1]: https://github.com/Thalhammer/jwt-cpp/actions?query=workflow%3A%22Coverage+CI%22 +[3.1]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.1.5/shields.json +[3.2]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.2.3/shields.json +[3.3]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/libressl/3.3.1/shields.json + +## Overview + +There is no hard dependency on a JSON library. Instead, there's a generic `jwt::basic_claim` which is templated around type traits, which described the semantic [JSON types](https://json-schema.org/understanding-json-schema/reference/type.html) for a value, object, array, string, number, integer and boolean, as well as methods to translate between them. + +```cpp +jwt::basic_claim claim(json::object({{"json", true},{"example", 0}})); +``` + +This allows for complete freedom when picking which libraries you want to use. For more information, [see below](#providing-your-own-json-traits-your-traits). + +In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. + +As for the base64 requirements of JWTs, this libary provides `base.h` with all the required implentation; However base64 implementations are very common, with varying degrees of performance. When providing your own base64 implementation, you can define `JWT_DISABLE_BASE64` to remove the jwt-cpp implementation. + +### Getting Started + +Simple example of decoding a token and printing all [claims](https://tools.ietf.org/html/rfc7519#section-4) ([try it out](https://github.com/Thalhammer/jwt-cpp/tree/master/example/print-claims.cpp)): + +```cpp +#include +#include + +int main() { + std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + auto decoded = jwt::decode(token); + + for(auto& e : decoded.get_payload_claims()) + std::cout << e.first << " = " << e.second << std::endl; +} +``` + +In order to verify a token you first build a verifier and use it to verify a decoded token. + +```cpp +auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{ "secret" }) + .with_issuer("auth0"); + +verifier.verify(decoded_token); +``` + +The created verifier is stateless so you can reuse it for different tokens. + +Creating a token (and signing) is equally as easy. + +```cpp +auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWS") + .set_payload_claim("sample", jwt::claim(std::string("test"))) + .sign(jwt::algorithm::hs256{"secret"}); +``` + +Here is a simple example of creating a token that will expire in one hour: + +```cpp +auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); +``` + +> To see more examples working with RSA public and private keys, visit our [examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example)! + +### Providing your own JSON Traits + +There are several key items that need to be provided to a `jwt::basic_claim` in order for it to be interoptable with you JSON library of choice. + +* type specifications +* conversion from generic "value type" to a specific type +* serialization and parsing + +If ever you are not sure, the traits are heavily checked against static asserts to make sure you provide everything that's required. + +> :warning: Not all JSON libraries are a like, you may need to extent certain types such that it can be used by jwt-cpp. See this [example](https://github.com/Thalhammer/jwt-cpp/blob/ac3de9e69bc698a464dacb256a1b50512843f092/tests/jsoncons/JsonconsTest.cpp). + +```cpp +struct my_favorite_json_library_traits { + // Type Specifications + using value_type = json; // The generic "value type" implementation, most libraries have one + using object_type = json::object_t; // The "map type" string to value + using array_type = json::array_t; // The "list type" array of values + using string_type = std::string; // The "list of chars", must be a narrow char + using number_type = double; // The "percision type" + using integer_type = int64_t; // The "integral type" + using boolean_type = bool; // The "boolean type" + + // Translation between the implementation notion of type, to the jwt::json::type equivilant + static jwt::json::type get_type(const value_type &val) { + using jwt::json::type; + + if (val.type() == json::value_t::object) + return type::object; + if (val.type() == json::value_t::array) + return type::array; + if (val.type() == json::value_t::string) + return type::string; + if (val.type() == json::value_t::number_float) + return type::number; + if (val.type() == json::value_t::number_integer) + return type::integer; + if (val.type() == json::value_t::boolean) + return type::boolean; + + throw std::logic_error("invalid type"); + } + + // Conversion from generic value to specific type + static object_type as_object(const value_type &val); + static array_type as_array(const value_type &val); + static string_type as_string(const value_type &val); + static number_type as_number(const value_type &val); + static integer_type as_int(const value_type &val); + static boolean_type as_bool(const value_type &val); + + // serilization and parsing + static bool parse(value_type &val, string_type str); + static string_type serialize(const value_type &val); // with no extra whitespace, padding or indentation +}; +``` + +## Contributing + +If you have an improvement or found a bug feel free to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or add the change and create a pull request. If you file a bug please make sure to include as much information about your environment (compiler version, etc.) as possible to help reproduce the issue. If you add a new feature please make sure to also include test cases for it. + +## Dependencies + +In order to use jwt-cpp you need the following tools. + +* libcrypto (openssl or compatible) +* libssl-dev (for the header files) +* a compiler supporting at least c++11 +* basic stl support + +In order to build the test cases you also need + +* gtest +* pthread + +## Troubleshooting + +### Expired tokens + +If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically, +if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, +which may be why your token is immediately expiring. Please see example above on the right way to use current time. + +### Missing \_HMAC and \_EVP_sha256 symbols on Mac + +There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. + +### Building on windows fails with syntax errors + +The header ``, which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. +See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: + +* define NOMINMAX, which suppresses this behaviour +* include this library before you include windows.h +* place `#undef max` and `#undef min` before you include this library diff --git a/dep/jwt-cpp/include/jwt-cpp/base.h b/dep/jwt-cpp/include/jwt-cpp/base.h new file mode 100644 index 000000000..c447113c9 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/base.h @@ -0,0 +1,208 @@ +#ifndef JWT_CPP_BASE_H +#define JWT_CPP_BASE_H + +#include +#include +#include + +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#define JWT_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#ifndef JWT_FALLTHROUGH +#define JWT_FALLTHROUGH +#endif + +namespace jwt { + /** + * \brief character maps when encoding and decoding + */ + namespace alphabet { + /** + * \brief valid list of characted when working with [Base64](https://tools.ietf.org/html/rfc3548) + */ + struct base64 { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + return data; + } + static const std::string& fill() { + static std::string fill{"="}; + return fill; + } + }; + /** + * \brief valid list of characted when working with [Base64URL](https://tools.ietf.org/html/rfc4648) + */ + struct base64url { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::string& fill() { + static std::string fill{"%3d"}; + return fill; + } + }; + } // namespace alphabet + + /** + * \brief Alphabet generic methods for working with encoding/decoding the base64 family + */ + class base { + public: + template + static std::string encode(const std::string& bin) { + return encode(bin, T::data(), T::fill()); + } + template + static std::string decode(const std::string& base) { + return decode(base, T::data(), T::fill()); + } + template + static std::string pad(const std::string& base) { + return pad(base, T::fill()); + } + template + static std::string trim(const std::string& base) { + return trim(base, T::fill()); + } + + private: + static std::string encode(const std::string& bin, const std::array& alphabet, + const std::string& fill) { + size_t size = bin.size(); + std::string res; + + // clear incomplete bytes + size_t fast_size = size - size % 3; + for (size_t i = 0; i < fast_size;) { + uint32_t octet_a = static_cast(bin[i++]); + uint32_t octet_b = static_cast(bin[i++]); + uint32_t octet_c = static_cast(bin[i++]); + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += alphabet[(triple >> 0 * 6) & 0x3F]; + } + + if (fast_size == size) return res; + + size_t mod = size % 3; + + uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + switch (mod) { + case 1: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += fill; + res += fill; + break; + case 2: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += fill; + break; + default: break; + } + + return res; + } + + static std::string decode(const std::string& base, const std::array& alphabet, + const std::string& fill) { + size_t size = base.size(); + + size_t fill_cnt = 0; + while (size > fill.size()) { + if (base.substr(size - fill.size(), fill.size()) == fill) { + fill_cnt++; + size -= fill.size(); + if (fill_cnt > 2) throw std::runtime_error("Invalid input"); + } else + break; + } + + if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input"); + + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { + for (size_t i = 0; i < alphabet.size(); i++) { + if (alphabet[i] == base[offset]) return static_cast(i); + } + throw std::runtime_error("Invalid input"); + }; + + size_t fast_size = size - size % 4; + for (size_t i = 0; i < fast_size;) { + uint32_t sextet_a = get_sextet(i++); + uint32_t sextet_b = get_sextet(i++); + uint32_t sextet_c = get_sextet(i++); + uint32_t sextet_d = get_sextet(i++); + + uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); + + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + res += static_cast((triple >> 0 * 8) & 0xFFU); + } + + if (fill_cnt == 0) return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); + + switch (fill_cnt) { + case 1: + triple |= (get_sextet(fast_size + 2) << 1 * 6); + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + break; + case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break; + default: break; + } + + return res; + } + + static std::string pad(const std::string& base, const std::string& fill) { + std::string padding; + switch (base.size() % 4) { + case 1: padding += fill; JWT_FALLTHROUGH; + case 2: padding += fill; JWT_FALLTHROUGH; + case 3: padding += fill; JWT_FALLTHROUGH; + default: break; + } + + return base + padding; + } + + static std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); + } + }; +} // namespace jwt + +#endif diff --git a/dep/jwt-cpp/include/jwt-cpp/jwt.h b/dep/jwt-cpp/include/jwt-cpp/jwt.h new file mode 100644 index 000000000..bd3e6e986 --- /dev/null +++ b/dep/jwt-cpp/include/jwt-cpp/jwt.h @@ -0,0 +1,3039 @@ +#ifndef JWT_CPP_JWT_H +#define JWT_CPP_JWT_H + +#ifndef JWT_DISABLE_PICOJSON +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" +#endif + +#ifndef JWT_DISABLE_BASE64 +#include "base.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201402L +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#endif + +// If openssl version less than 1.1 +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define OPENSSL10 +#endif + +// If openssl version less than 1.1.1 +#if OPENSSL_VERSION_NUMBER < 0x10101000L +#define OPENSSL110 +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) +#define OPENSSL10 +#define OPENSSL110 +#endif + +#ifndef JWT_CLAIM_EXPLICIT +#define JWT_CLAIM_EXPLICIT explicit +#endif + +/** + * \brief JSON Web Token + * + * A namespace to contain everything related to handling JSON Web Tokens, JWT for short, + * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for + * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515) + */ +namespace jwt { + using date = std::chrono::system_clock::time_point; + + /** + * \brief Everything related to error codes issued by the library + */ + namespace error { + struct signature_verification_exception : public std::system_error { + using system_error::system_error; + }; + struct signature_generation_exception : public std::system_error { + using system_error::system_error; + }; + struct rsa_exception : public std::system_error { + using system_error::system_error; + }; + struct ecdsa_exception : public std::system_error { + using system_error::system_error; + }; + struct token_verification_exception : public std::system_error { + using system_error::system_error; + }; + /** + * \brief Errors related to processing of RSA signatures + */ + enum class rsa_error { + ok = 0, + cert_load_failed = 10, + get_key_failed, + write_key_failed, + write_cert_failed, + convert_to_pem_failed, + load_key_bio_write, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided + }; + /** + * \brief Error category for RSA errors + */ + inline std::error_category& rsa_error_category() { + class rsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "rsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case rsa_error::ok: return "no error"; + case rsa_error::cert_load_failed: return "error loading cert into memory"; + case rsa_error::get_key_failed: return "error getting key from certificate"; + case rsa_error::write_key_failed: return "error writing key data in PEM format"; + case rsa_error::write_cert_failed: return "error writing cert data in PEM format"; + case rsa_error::convert_to_pem_failed: return "failed to convert key to pem"; + case rsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case rsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case rsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case rsa_error::no_key_provided: return "at least one of public or private key need to be present"; + default: return "unknown RSA error"; + } + } + }; + static rsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; } + /** + * \brief Errors related to processing of RSA signatures + */ + enum class ecdsa_error { + ok = 0, + load_key_bio_write = 10, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided, + invalid_key_size, + invalid_key + }; + /** + * \brief Error category for ECDSA errors + */ + inline std::error_category& ecdsa_error_category() { + class ecdsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "ecdsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case ecdsa_error::ok: return "no error"; + case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case ecdsa_error::no_key_provided: + return "at least one of public or private key need to be present"; + case ecdsa_error::invalid_key_size: return "invalid key size"; + case ecdsa_error::invalid_key: return "invalid key"; + default: return "unknown ECDSA error"; + } + } + }; + static ecdsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } + + /** + * \brief Errors related to verification of signatures + */ + enum class signature_verification_error { + ok = 0, + invalid_signature = 10, + create_context_failed, + verifyinit_failed, + verifyupdate_failed, + verifyfinal_failed, + get_key_failed + }; + /** + * \brief Error category for verification errors + */ + inline std::error_category& signature_verification_error_category() { + class verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_verification_error::ok: return "no error"; + case signature_verification_error::invalid_signature: return "invalid signature"; + case signature_verification_error::create_context_failed: + return "failed to verify signature: could not create context"; + case signature_verification_error::verifyinit_failed: + return "failed to verify signature: VerifyInit failed"; + case signature_verification_error::verifyupdate_failed: + return "failed to verify signature: VerifyUpdate failed"; + case signature_verification_error::verifyfinal_failed: + return "failed to verify signature: VerifyFinal failed"; + case signature_verification_error::get_key_failed: + return "failed to verify signature: Could not get key"; + default: return "unknown signature verification error"; + } + } + }; + static verification_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(signature_verification_error e) { + return {static_cast(e), signature_verification_error_category()}; + } + + /** + * \brief Errors related to signature generation errors + */ + enum class signature_generation_error { + ok = 0, + hmac_failed = 10, + create_context_failed, + signinit_failed, + signupdate_failed, + signfinal_failed, + ecdsa_do_sign_failed, + digestinit_failed, + digestupdate_failed, + digestfinal_failed, + rsa_padding_failed, + rsa_private_encrypt_failed, + get_key_failed + }; + /** + * \brief Error category for signature generation errors + */ + inline std::error_category& signature_generation_error_category() { + class signature_generation_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_generation_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_generation_error::ok: return "no error"; + case signature_generation_error::hmac_failed: return "hmac failed"; + case signature_generation_error::create_context_failed: + return "failed to create signature: could not create context"; + case signature_generation_error::signinit_failed: + return "failed to create signature: SignInit failed"; + case signature_generation_error::signupdate_failed: + return "failed to create signature: SignUpdate failed"; + case signature_generation_error::signfinal_failed: + return "failed to create signature: SignFinal failed"; + case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature"; + case signature_generation_error::digestinit_failed: + return "failed to create signature: DigestInit failed"; + case signature_generation_error::digestupdate_failed: + return "failed to create signature: DigestUpdate failed"; + case signature_generation_error::digestfinal_failed: + return "failed to create signature: DigestFinal failed"; + case signature_generation_error::rsa_padding_failed: + return "failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"; + case signature_generation_error::rsa_private_encrypt_failed: + return "failed to create signature: RSA_private_encrypt failed"; + case signature_generation_error::get_key_failed: + return "failed to generate signature: Could not get key"; + default: return "unknown signature generation error"; + } + } + }; + static signature_generation_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(signature_generation_error e) { + return {static_cast(e), signature_generation_error_category()}; + } + + /** + * \brief Errors related to token verification errors + */ + enum class token_verification_error { + ok = 0, + wrong_algorithm = 10, + missing_claim, + claim_type_missmatch, + claim_value_missmatch, + token_expired, + audience_missmatch + }; + /** + * \brief Error category for token verification errors + */ + inline std::error_category& token_verification_error_category() { + class token_verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "token_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case token_verification_error::ok: return "no error"; + case token_verification_error::wrong_algorithm: return "wrong algorithm"; + case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)"; + case token_verification_error::claim_type_missmatch: + return "claim type does not match expected type"; + case token_verification_error::claim_value_missmatch: + return "claim value does not match expected value"; + case token_verification_error::token_expired: return "token expired"; + case token_verification_error::audience_missmatch: + return "token doesn't contain the required audience"; + default: return "unknown token verification error"; + } + } + }; + static token_verification_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(token_verification_error e) { + return {static_cast(e), token_verification_error_category()}; + } + + inline void throw_if_error(std::error_code ec) { + if (ec) { + if (ec.category() == rsa_error_category()) throw rsa_exception(ec); + if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec); + if (ec.category() == signature_verification_error_category()) + throw signature_verification_exception(ec); + if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec); + if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec); + } + } + } // namespace error + + // FIXME: Remove + // Keep backward compat at least for a couple of revisions + using error::ecdsa_exception; + using error::rsa_exception; + using error::signature_generation_exception; + using error::signature_verification_exception; + using error::token_verification_exception; +} // namespace jwt +namespace std { + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; +} // namespace std +namespace jwt { + /** + * \brief A collection for working with certificates + * + * These _helpers_ are usefully when working with certificates OpenSSL APIs. + * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517] + * you maybe need to extract the modulus and exponent of an RSA Public Key. + */ + namespace helper { + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, + std::error_code& ec) { + ec.clear(); +#if OPENSSL_VERSION_NUMBER <= 0x10100003L + std::unique_ptr certbio( + BIO_new_mem_buf(const_cast(certstr.data()), static_cast(certstr.size())), BIO_free_all); +#else + std::unique_ptr certbio( + BIO_new_mem_buf(certstr.data(), static_cast(certstr.size())), BIO_free_all); +#endif + std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!certbio || !keybio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + std::unique_ptr cert( + PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + if (!cert) { + ec = error::rsa_error::cert_load_failed; + return {}; + } + std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); + if (!key) { + ec = error::rsa_error::get_key_failed; + return {}; + } + if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { + ec = error::rsa_error::write_key_failed; + return {}; + } + char* ptr = nullptr; + auto len = BIO_get_mem_data(keybio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + return {ptr, static_cast(len)}; + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { + std::error_code ec; + auto res = extract_pubkey_from_cert(certstr, pw, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, + std::error_code& ec) { + ec.clear(); + const auto decodedStr = decode(cert_base64_der_str); + auto c_str = reinterpret_cast(decodedStr.c_str()); + + std::unique_ptr cert(d2i_X509(NULL, &c_str, decodedStr.size()), X509_free); + std::unique_ptr certbio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!cert || !certbio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + if (!PEM_write_bio_X509(certbio.get(), cert.get())) { + ec = error::rsa_error::write_cert_failed; + return {}; + } + + char* ptr = nullptr; + const auto len = BIO_get_mem_data(certbio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + + return {ptr, static_cast(len)}; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { + auto decode = [](const std::string& token) { + return base::decode(base::pad(token)); + }; + return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \throw rsa_exception if an error occurred + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, ec); + error::throw_if_error(ec); + return res; + } +#endif + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::shared_ptr load_public_key_from_string(const std::string& key, + const std::string& password, std::error_code& ec) { + ec.clear(); + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!pubkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return nullptr; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return nullptr; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + } + + std::shared_ptr pkey( + PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, + (void*)password.data()), // NOLINT(google-readability-casting) requires `const_cast` + EVP_PKEY_free); + if (!pkey) { + ec = error::rsa_error::load_key_bio_read; + return nullptr; + } + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param certstr String containing the certificate or key encoded as pem + * \param pw Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::shared_ptr load_public_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_public_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param pw Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occures) + */ + inline std::shared_ptr + load_private_key_from_string(const std::string& key, const std::string& password, std::error_code& ec) { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!privkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return nullptr; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return nullptr; + } + std::shared_ptr pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str())), + EVP_PKEY_free); + if (!pkey) { + ec = error::rsa_error::load_key_bio_read; + return nullptr; + } + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param pw Password used to decrypt key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::shared_ptr load_private_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_private_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * Convert a OpenSSL BIGNUM to a std::string + * \param bn BIGNUM to convert + * \return bignum as string + */ + inline +#ifdef OPENSSL10 + static std::string + bn2raw(BIGNUM* bn) +#else + static std::string + bn2raw(const BIGNUM* bn) +#endif + { + std::string res(BN_num_bytes(bn), '\0'); + BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` + return res; + } + /** + * Convert an std::string to a OpenSSL BIGNUM + * \param raw String to convert + * \return BIGNUM representation + */ + inline static std::unique_ptr raw2bn(const std::string& raw) { + return std::unique_ptr( + BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), + BN_free); + } + } // namespace helper + + /** + * \brief Various cryptographic algorithms when working with JWT + * + * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or + * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by + * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE + * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA (JSON Web + * Algorithms)(https://tools.ietf.org/html/rfc7518#section-3.1) + */ + namespace algorithm { + /** + * \brief "none" algorithm. + * + * Returns and empty signature and checks if the given signature is empty. + */ + struct none { + /** + * \brief Return an empty string + */ + std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + ec.clear(); + return {}; + } + /** + * \brief Check if the given signature is empty. + * + * JWT's with "none" algorithm should not contain a signature. + * \param signature Signature data to verify + * \param ec error_code filled with details about the error + */ + void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const { + ec.clear(); + if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; } + } + /// Get algorithm name + std::string name() const { return "none"; } + }; + /** + * \brief Base class for HMAC family of algorithms + */ + struct hmacsha { + /** + * Construct new hmac algorithm + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) + : secret(std::move(key)), md(md), alg_name(std::move(name)) {} + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return HMAC signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); + auto len = static_cast(res.size()); + if (HMAC(md(), secret.data(), static_cast(secret.size()), + reinterpret_cast(data.data()), static_cast(data.size()), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == nullptr) { + ec = error::signature_generation_error::hmac_failed; + return {}; + } + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details about failure. + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto res = sign(data, ec); + if (ec) return; + + bool matched = true; + for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) + if (res[i] != signature[i]) matched = false; + if (res.size() != signature.size()) matched = false; + if (!matched) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// HMAC secrect + const std::string secret; + /// HMAC hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for RSA family of algorithms + */ + struct rsa { + /** + * Construct new rsa algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception(error::rsa_error::no_key_provided); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return RSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_SignInit(ctx.get(), md())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + std::string res(EVP_PKEY_size(pkey.get()), '\0'); + unsigned int len = 0; + + if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on failure + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_VerifyInit(ctx.get(), md())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + static_cast(signature.size()), pkey.get()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing converted keys + std::shared_ptr pkey; + /// Hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for ECDSA family of algorithms + */ + struct ecdsa { + /** + * Construct new ecdsa algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. \param public_key_password Password to decrypt public key pem. \param private_key_password Password + * to decrypt private key pem. \param md Pointer to hash function \param name Name of the algorithm + */ + ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) + : md(md), alg_name(std::move(name)), signature_length(siglen) { + if (!public_key.empty()) { + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!pubkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); + if (public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(public_key, public_key_password); + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + } else { + const int len = static_cast(public_key.size()); + if (BIO_write(pubkey_bio.get(), public_key.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + } + + pkey.reset(PEM_read_bio_EC_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)public_key_password + .c_str()), // NOLINT(google-readability-casting) requires `const_cast` + EC_KEY_free); + if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + + if (!private_key.empty()) { + std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if (!privkey_bio) throw ecdsa_exception(error::ecdsa_error::create_mem_bio_failed); + const int len = static_cast(private_key.size()); + if (BIO_write(privkey_bio.get(), private_key.data(), len) != len) + throw ecdsa_exception(error::ecdsa_error::load_key_bio_write); + pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, + const_cast(private_key_password.c_str())), + EC_KEY_free); + if (!pkey) throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + size_t keysize = EC_GROUP_get_degree(EC_KEY_get0_group(pkey.get())); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + if (!pkey) throw ecdsa_exception(error::ecdsa_error::no_key_provided); + + if (EC_KEY_check_key(pkey.get()) == 0) throw ecdsa_exception(error::ecdsa_error::invalid_key); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + const std::string hash = generate_hash(data, ec); + if (ec) return {}; + + std::unique_ptr sig( + ECDSA_do_sign(reinterpret_cast(hash.data()), static_cast(hash.size()), + pkey.get()), + ECDSA_SIG_free); + if (!sig) { + ec = error::signature_generation_error::ecdsa_do_sign_failed; + return {}; + } +#ifdef OPENSSL10 + + auto rr = helper::bn2raw(sig->r); + auto rs = helper::bn2raw(sig->s); +#else + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig.get(), &r, &s); + auto rr = helper::bn2raw(r); + auto rs = helper::bn2raw(s); +#endif + if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) + throw std::logic_error("bignum size exceeded expected length"); + rr.insert(0, signature_length / 2 - rr.size(), '\0'); + rs.insert(0, signature_length / 2 - rs.size(), '\0'); + return rr + rs; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + const std::string hash = generate_hash(data, ec); + if (ec) return; + auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + +#ifdef OPENSSL10 + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + + if (ECDSA_do_verify((const unsigned char*)hash.data(), static_cast(hash.size()), &sig, + pkey.get()) != 1) { + ec = error::signature_verification_error::invalid_signature; + return; + } +#else + std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); + if (!sig) { + ec = error::signature_verification_error::create_context_failed; + return; + } + + ECDSA_SIG_set0(sig.get(), r.release(), s.release()); + + if (ECDSA_do_verify(reinterpret_cast(hash.data()), static_cast(hash.size()), + sig.get(), pkey.get()) != 1) { + ec = error::signature_verification_error::invalid_signature; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data, std::error_code& ec) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), + &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (EVP_DigestInit(ctx.get(), md()) == 0) { + ec = error::signature_generation_error::digestinit_failed; + return {}; + } + if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + unsigned int len = 0; + std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); + if (EVP_DigestFinal( + ctx.get(), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == 0) { + ec = error::signature_generation_error::digestfinal_failed; + return {}; + } + res.resize(len); + return res; + } + + /// OpenSSL struct containing keys + std::shared_ptr pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + /// Length of the resulting signature + const size_t signature_length; + }; + +#ifndef OPENSSL110 + /** + * \brief Base class for EdDSA family of algorithms + * + * https://tools.ietf.org/html/rfc8032 + * + * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html), + * so these algorithms are only available when building against this version or higher. + */ + struct eddsa { + /** + * Construct new eddsa algorithm + * \param public_key EdDSA public key in PEM format + * \param private_key EdDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + * \param name Name of the algorithm + */ + eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, std::string name) + : alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw ecdsa_exception(error::ecdsa_error::load_key_bio_read); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return EdDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + size_t len = EVP_PKEY_size(pkey.get()); + std::string res(len, '\0'); + +// LibreSSL is the special kid in the block, as it does not support EVP_DigestSign. +// OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this +// mess. +#ifdef LIBRESSL_VERSION_NUMBER + ERR_clear_error(); + if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != + 1) { + std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#else + if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len, + reinterpret_cast(data.data()), data.size()) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#endif + + res.resize(len); + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } +// LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify. +// OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this +// mess. +#ifdef LIBRESSL_VERSION_NUMBER + if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()), + data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + signature.size()) != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#else + auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()), + signature.size(), reinterpret_cast(data.data()), + data.size()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL struct containing keys + std::shared_ptr pkey; + /// algorithm's name + const std::string alg_name; + }; +#endif + /** + * \brief Base class for PSS-RSA family of algorithms + */ + struct pss { + /** + * Construct new pss algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw rsa_exception(error::rsa_error::no_key_provided); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto hash = this->generate_hash(data, ec); + if (ec) return {}; + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + if (!key) { + ec = error::signature_generation_error::get_key_failed; + return {}; + } + const int size = RSA_size(key.get()); + + std::string padded(size, 0x00); + if (RSA_padding_add_PKCS1_PSS_mgf1( + key.get(), (unsigned char*)padded.data(), reinterpret_cast(hash.data()), + md(), md(), -1) == 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::rsa_padding_failed; + return {}; + } + + std::string res(size, 0x00); + if (RSA_private_encrypt(size, reinterpret_cast(padded.data()), + (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < + 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::rsa_private_encrypt_failed; + return {}; + } + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with error details + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto hash = this->generate_hash(data, ec); + if (ec) return; + + std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); + if (!key) { + ec = error::signature_verification_error::get_key_failed; + return; + } + const int size = RSA_size(key.get()); + + std::string sig(size, 0x00); + if (RSA_public_decrypt( + static_cast(signature.size()), reinterpret_cast(signature.data()), + (unsigned char*)sig.data(), // NOLINT(google-readability-casting) requires `const_cast` + key.get(), RSA_NO_PADDING) == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + + if (RSA_verify_PKCS1_PSS_mgf1(key.get(), reinterpret_cast(hash.data()), md(), + md(), reinterpret_cast(sig.data()), -1) == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /** + * Hash the provided data using the hash function specified in constructor + * \param data Data to hash + * \return Hash of data + */ + std::string generate_hash(const std::string& data, std::error_code& ec) const { +#ifdef OPENSSL10 + std::unique_ptr ctx(EVP_MD_CTX_create(), + &EVP_MD_CTX_destroy); +#else + std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); +#endif + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (EVP_DigestInit(ctx.get(), md()) == 0) { + ec = error::signature_generation_error::digestinit_failed; + return {}; + } + if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + unsigned int len = 0; + std::string res(EVP_MD_CTX_size(ctx.get()), '\0'); + if (EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == + 0) { // NOLINT(google-readability-casting) requires `const_cast` + ec = error::signature_generation_error::digestfinal_failed; + return {}; + } + res.resize(len); + return res; + } + + /// OpenSSL structure containing keys + std::shared_ptr pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + + /** + * HS256 algorithm + */ + struct hs256 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + }; + /** + * HS384 algorithm + */ + struct hs384 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + }; + /** + * HS512 algorithm + */ + struct hs512 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + }; + /** + * RS256 algorithm + */ + struct rs256 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} + }; + /** + * RS384 algorithm + */ + struct rs384 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} + }; + /** + * RS512 algorithm + */ + struct rs512 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} + }; + /** + * ES256 algorithm + */ + struct es256 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} + }; + /** + * ES384 algorithm + */ + struct es384 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} + }; + /** + * ES512 algorithm + */ + struct es512 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} + }; + +#ifndef OPENSSL110 + /** + * Ed25519 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed25519 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed25519 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed25519 public key in PEM format + * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed25519(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; + + /** + * Ed448 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed448 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed448 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed448 public key in PEM format + * \param private_key Ed448 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed448(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; +#endif + + /** + * PS256 algorithm + */ + struct ps256 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} + }; + /** + * PS384 algorithm + */ + struct ps384 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} + }; + /** + * PS512 algorithm + */ + struct ps512 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} + }; + } // namespace algorithm + + /** + * \brief JSON Abstractions for working with any library + */ + namespace json { + /** + * \brief Generic JSON types used in JWTs + * + * This enum is to abstract the third party underlying types + */ + enum class type { boolean, integer, number, string, array, object }; + } // namespace json + + namespace details { +#ifdef __cpp_lib_void_t + template + using void_t = std::void_t; +#else + // https://en.cppreference.com/w/cpp/types/void_t + template + struct make_void { + using type = void; + }; + + template + using void_t = typename make_void::type; +#endif + +#ifdef __cpp_lib_experimental_detect + template class _Op, typename... _Args> + using is_detected = std::experimental::is_detected<_Op, _Args...>; + + template class _Op, typename... _Args> + using is_detected_t = std::experimental::detected_t<_Op, _Args...>; +#else + struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; + }; + + // https://en.cppreference.com/w/cpp/experimental/is_detected + template class Op, class... Args> + struct detector { + using value = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value; + + template class Op, class... Args> + using is_detected_t = typename detector::type; +#endif + + template + using get_type_function = decltype(traits_type::get_type); + + template + using is_get_type_signature = + typename std::is_same, json::type(const value_type&)>; + + template + struct supports_get_type { + static constexpr auto value = is_detected::value && + std::is_function>::value && + is_get_type_signature::value; + }; + + template + using as_object_function = decltype(traits_type::as_object); + + template + using is_as_object_signature = + typename std::is_same, object_type(const value_type&)>; + + template + struct supports_as_object { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_object_signature::value; + }; + + template + using as_array_function = decltype(traits_type::as_array); + + template + using is_as_array_signature = + typename std::is_same, array_type(const value_type&)>; + + template + struct supports_as_array { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_array_signature::value; + }; + + template + using as_string_function = decltype(traits_type::as_string); + + template + using is_as_string_signature = + typename std::is_same, string_type(const value_type&)>; + + template + struct supports_as_string { + static constexpr auto value = std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_string_signature::value; + }; + + template + using as_number_function = decltype(traits_type::as_number); + + template + using is_as_number_signature = + typename std::is_same, number_type(const value_type&)>; + + template + struct supports_as_number { + static constexpr auto value = std::is_floating_point::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_number_signature::value; + }; + + template + using as_integer_function = decltype(traits_type::as_int); + + template + using is_as_integer_signature = + typename std::is_same, integer_type(const value_type&)>; + + template + struct supports_as_integer { + static constexpr auto value = std::is_signed::value && + !std::is_floating_point::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_integer_signature::value; + }; + + template + using as_boolean_function = decltype(traits_type::as_bool); + + template + using is_as_boolean_signature = + typename std::is_same, boolean_type(const value_type&)>; + + template + struct supports_as_boolean { + static constexpr auto value = std::is_convertible::value && + std::is_constructible::value && + is_detected::value && + std::is_function>::value && + is_as_boolean_signature::value; + }; + + template + struct is_valid_traits { + // Internal assertions for better feedback + static_assert(supports_get_type::value, + "traits must provide `jwt::json::type get_type(const value_type&)`"); + static_assert(supports_as_object::value, + "traits must provide `object_type as_object(const value_type&)`"); + static_assert(supports_as_array::value, + "traits must provide `array_type as_array(const value_type&)`"); + static_assert(supports_as_string::value, + "traits must provide `string_type as_string(const value_type&)`"); + static_assert(supports_as_number::value, + "traits must provide `number_type as_number(const value_type&)`"); + static_assert( + supports_as_integer::value, + "traits must provide `integer_type as_int(const value_type&)`"); + static_assert( + supports_as_boolean::value, + "traits must provide `boolean_type as_bool(const value_type&)`"); + + static constexpr auto value = + supports_get_type::value && + supports_as_object::value && + supports_as_array::value && + supports_as_string::value && + supports_as_number::value && + supports_as_integer::value && + supports_as_boolean::value; + }; + + template + struct is_valid_json_value { + static constexpr auto value = + std::is_default_constructible::value && + std::is_constructible::value && // a more generic is_copy_constructible + std::is_move_constructible::value && std::is_assignable::value && + std::is_copy_assignable::value && std::is_move_assignable::value; + // TODO(cmcarthur): Stream operators + }; + + template + using has_mapped_type = typename traits_type::mapped_type; + + template + using has_key_type = typename traits_type::key_type; + + template + using has_value_type = typename traits_type::value_type; + + template + using has_iterator = typename object_type::iterator; + + template + using has_const_iterator = typename object_type::const_iterator; + + template + using is_begin_signature = + typename std::is_same().begin()), has_iterator>; + + template + using is_begin_const_signature = + typename std::is_same().begin()), has_const_iterator>; + + template + struct supports_begin { + static constexpr auto value = + is_detected::value && is_detected::value && + is_begin_signature::value && is_begin_const_signature::value; + }; + + template + using is_end_signature = + typename std::is_same().end()), has_iterator>; + + template + using is_end_const_signature = + typename std::is_same().end()), has_const_iterator>; + + template + struct supports_end { + static constexpr auto value = + is_detected::value && is_detected::value && + is_end_signature::value && is_end_const_signature::value; + }; + + template + using is_count_signature = typename std::is_integral().count(std::declval()))>; + + template + using is_subcription_operator_signature = + typename std::is_same()[std::declval()]), + value_type&>; + + template + using is_at_const_signature = + typename std::is_same().at(std::declval())), + const value_type&>; + + template + struct is_valid_json_object { + static constexpr auto value = + is_detected::value && + std::is_same::value && + is_detected::value && + std::is_same::value && + supports_begin::value && supports_end::value && + is_count_signature::value && + is_subcription_operator_signature::value && + is_at_const_signature::value; + + static constexpr auto supports_claims_transform = + value && is_detected::value && + std::is_same>::value; + }; + + template + struct is_valid_json_array { + static constexpr auto value = std::is_same::value; + }; + + template + struct is_valid_json_types { + // Internal assertions for better feedback + static_assert(is_valid_json_value::value, + "value type must meet basic requirements, default constructor, copyable, moveable"); + static_assert(is_valid_json_object::value, + "object_type must be a string_type to value_type container"); + static_assert(is_valid_json_array::value, + "array_type must be a container of value_type"); + + static constexpr auto value = is_valid_json_object::value && + is_valid_json_value::value && + is_valid_json_array::value; + }; + } // namespace details + + /** + * \brief a class to store a generic JSON value as claim + * + * The default template parameters use [picojson](https://github.com/kazuho/picojson) + * + * \tparam json_traits : JSON implementation traits + * + * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) + */ + template + class basic_claim { + /** + * The reason behind this is to provide an expressive abstraction without + * over complexifying the API. For more information take the time to read + * https://github.com/nlohmann/json/issues/774. It maybe be expanded to + * support custom string types. + */ + static_assert(std::is_same::value, + "string_type must be a std::string."); + + static_assert( + details::is_valid_json_types::value, + "must staisfy json container requirements"); + static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); + + typename json_traits::value_type val; + + public: + using set_t = std::set; + + basic_claim() = default; + basic_claim(const basic_claim&) = default; + basic_claim(basic_claim&&) = default; + basic_claim& operator=(const basic_claim&) = default; + basic_claim& operator=(basic_claim&&) = default; + ~basic_claim() = default; + + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} + JWT_CLAIM_EXPLICIT basic_claim(const date& d) + : val(typename json_traits::integer_type(std::chrono::system_clock::to_time_t(d))) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::array_type a) : val(std::move(a)) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::value_type v) : val(std::move(v)) {} + JWT_CLAIM_EXPLICIT basic_claim(const set_t& s) : val(typename json_traits::array_type(s.begin(), s.end())) {} + template + basic_claim(Iterator begin, Iterator end) : val(typename json_traits::array_type(begin, end)) {} + + /** + * Get wrapped JSON value + * \return Wrapped JSON value + */ + typename json_traits::value_type to_json() const { return val; } + + /** + * Parse input stream into underlying JSON value + * \return input stream + */ + std::istream& operator>>(std::istream& is) { return is >> val; } + + /** + * Serialize claim to output stream from wrapped JSON value + * \return ouput stream + */ + std::ostream& operator<<(std::ostream& os) { return os << val; } + + /** + * Get type of contained JSON value + * \return Type + * \throw std::logic_error An internal error occured + */ + json::type get_type() const { return json_traits::get_type(val); } + + /** + * Get the contained JSON value as a string + * \return content as string + * \throw std::bad_cast Content was not a string + */ + typename json_traits::string_type as_string() const { return json_traits::as_string(val); } + + /** + * Get the contained JSON value as a date + * \return content as date + * \throw std::bad_cast Content was not a date + */ + date as_date() const { return std::chrono::system_clock::from_time_t(as_int()); } + + /** + * Get the contained JSON value as an array + * \return content as array + * \throw std::bad_cast Content was not an array + */ + typename json_traits::array_type as_array() const { return json_traits::as_array(val); } + + /** + * Get the contained JSON value as a set of strings + * \return content as set of strings + * \throw std::bad_cast Content was not an array of string + */ + set_t as_set() const { + set_t res; + for (const auto& e : json_traits::as_array(val)) { + res.insert(json_traits::as_string(e)); + } + return res; + } + + /** + * Get the contained JSON value as an integer + * \return content as int + * \throw std::bad_cast Content was not an int + */ + typename json_traits::integer_type as_int() const { return json_traits::as_int(val); } + + /** + * Get the contained JSON value as a bool + * \return content as bool + * \throw std::bad_cast Content was not a bool + */ + typename json_traits::boolean_type as_bool() const { return json_traits::as_bool(val); } + + /** + * Get the contained JSON value as a number + * \return content as double + * \throw std::bad_cast Content was not a number + */ + typename json_traits::number_type as_number() const { return json_traits::as_number(val); } + }; + + namespace error { + struct invalid_json_exception : public std::runtime_error { + invalid_json_exception() : runtime_error("invalid json") {} + }; + struct claim_not_present_exception : public std::out_of_range { + claim_not_present_exception() : out_of_range("claim not found") {} + }; + } // namespace error + + namespace details { + template + class map_of_claims { + typename json_traits::object_type claims; + + public: + using basic_claim_t = basic_claim; + using iterator = typename json_traits::object_type::iterator; + using const_iterator = typename json_traits::object_type::const_iterator; + + map_of_claims() = default; + map_of_claims(const map_of_claims&) = default; + map_of_claims(map_of_claims&&) = default; + map_of_claims& operator=(const map_of_claims&) = default; + map_of_claims& operator=(map_of_claims&&) = default; + + map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} + + iterator begin() { return claims.begin(); } + iterator end() { return claims.end(); } + const_iterator cbegin() const { return claims.begin(); } + const_iterator cend() const { return claims.end(); } + const_iterator begin() const { return claims.begin(); } + const_iterator end() const { return claims.end(); } + + /** + * \brief Parse a JSON string into a map of claims + * + * The implication is that a "map of claims" is identic to a JSON object + * + * \param str JSON data to be parse as an object + * \return content as JSON object + */ + static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { + typename json_traits::value_type val; + if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); + + return json_traits::as_object(val); + }; + + /** + * Check if a claim is present in the map + * \return true if claim was present, false otherwise + */ + bool has_claim(const typename json_traits::string_type& name) const noexcept { + return claims.count(name) != 0; + } + + /** + * Get a claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_claim(const typename json_traits::string_type& name) const { + if (!has_claim(name)) throw error::claim_not_present_exception(); + return basic_claim_t{claims.at(name)}; + } + + std::unordered_map get_claims() const { + static_assert( + details::is_valid_json_object::supports_claims_transform, + "currently there is a limitation on the internal implemantation of the `object_type` to have an " + "`std::pair` like `value_type`"); + + std::unordered_map res; + std::transform(claims.begin(), claims.end(), std::inserter(res, res.end()), + [](const typename json_traits::object_type::value_type& val) { + return std::make_pair(val.first, basic_claim_t{val.second}); + }); + return res; + } + }; + } // namespace details + + /** + * Base class that represents a token payload. + * Contains Convenience accessors for common claims. + */ + template + class payload { + protected: + details::map_of_claims payload_claims; + + public: + using basic_claim_t = basic_claim; + + /** + * Check if issuer is present ("iss") + * \return true if present, false otherwise + */ + bool has_issuer() const noexcept { return has_payload_claim("iss"); } + /** + * Check if subject is present ("sub") + * \return true if present, false otherwise + */ + bool has_subject() const noexcept { return has_payload_claim("sub"); } + /** + * Check if audience is present ("aud") + * \return true if present, false otherwise + */ + bool has_audience() const noexcept { return has_payload_claim("aud"); } + /** + * Check if expires is present ("exp") + * \return true if present, false otherwise + */ + bool has_expires_at() const noexcept { return has_payload_claim("exp"); } + /** + * Check if not before is present ("nbf") + * \return true if present, false otherwise + */ + bool has_not_before() const noexcept { return has_payload_claim("nbf"); } + /** + * Check if issued at is present ("iat") + * \return true if present, false otherwise + */ + bool has_issued_at() const noexcept { return has_payload_claim("iat"); } + /** + * Check if token id is present ("jti") + * \return true if present, false otherwise + */ + bool has_id() const noexcept { return has_payload_claim("jti"); } + /** + * Get issuer claim + * \return issuer as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } + /** + * Get subject claim + * \return subject as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } + /** + * Get audience claim + * \return audience as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_audience() const { + auto aud = get_payload_claim("aud"); + if (aud.get_type() == json::type::string) return {aud.as_string()}; + + return aud.as_set(); + } + /** + * Get expires claim + * \return expires as a date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_expires_at() const { return get_payload_claim("exp").as_date(); } + /** + * Get not valid before claim + * \return nbf date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_not_before() const { return get_payload_claim("nbf").as_date(); } + /** + * Get issued at claim + * \return issued at as date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_issued_at() const { return get_payload_claim("iat").as_date(); } + /** + * Get id claim + * \return id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } + /** + * Check if a payload claim is present + * \return true if claim was present, false otherwise + */ + bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { + return payload_claims.has_claim(name); + } + /** + * Get payload claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return payload_claims.get_claim(name); + } + }; + + /** + * Base class that represents a token header. + * Contains Convenience accessors for common claims. + */ + template + class header { + protected: + details::map_of_claims header_claims; + + public: + using basic_claim_t = basic_claim; + /** + * Check if algortihm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_header_claim("alg"); } + /** + * Check if type is present ("typ") + * \return true if present, false otherwise + */ + bool has_type() const noexcept { return has_header_claim("typ"); } + /** + * Check if content type is present ("cty") + * \return true if present, false otherwise + */ + bool has_content_type() const noexcept { return has_header_claim("cty"); } + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_header_claim("kid"); } + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } + /** + * Get type claim + * \return type as a string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } + /** + * Get content type claim + * \return content type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } + /** + * Check if a header claim is present + * \return true if claim was present, false otherwise + */ + bool has_header_claim(const typename json_traits::string_type& name) const noexcept { + return header_claims.has_claim(name); + } + /** + * Get header claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return header_claims.get_claim(name); + } + }; + + /** + * Class containing all information about a decoded token + */ + template + class decoded_jwt : public header, public payload { + protected: + /// Unmodifed token, as passed to constructor + const typename json_traits::string_type token; + /// Header part decoded from base64 + typename json_traits::string_type header; + /// Unmodified header part in base64 + typename json_traits::string_type header_base64; + /// Payload part decoded from base64 + typename json_traits::string_type payload; + /// Unmodified payload part in base64 + typename json_traits::string_type payload_base64; + /// Signature part decoded from base64 + typename json_traits::string_type signature; + /// Unmodified signature part in base64 + typename json_traits::string_type signature_base64; + + public: + using basic_claim_t = basic_claim; +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Parses a given token + * + * \note Decodes using the jwt::base64url which supports an std::string + * + * \param token The token to parse + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) + : decoded_jwt(token, [](const typename json_traits::string_type& token) { + return base::decode(base::pad(token)); + }) {} +#endif + /** + * \brief Parses a given token + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token The token to parse + * \param decode The function to decode the token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt(const typename json_traits::string_type& token, Decode decode) : token(token) { + auto hdr_end = token.find('.'); + if (hdr_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + auto payload_end = token.find('.', hdr_end + 1); + if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + header_base64 = token.substr(0, hdr_end); + payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature_base64 = token.substr(payload_end + 1); + + header = decode(header_base64); + payload = decode(payload_base64); + signature = decode(signature_base64); + + this->header_claims = details::map_of_claims::parse_claims(header); + this->payload_claims = details::map_of_claims::parse_claims(payload); + } + + /** + * Get token string, as passed to constructor + * \return token as passed to constructor + */ + const typename json_traits::string_type& get_token() const noexcept { return token; } + /** + * Get header part as json string + * \return header part after base64 decoding + */ + const typename json_traits::string_type& get_header() const noexcept { return header; } + /** + * Get payload part as json string + * \return payload part after base64 decoding + */ + const typename json_traits::string_type& get_payload() const noexcept { return payload; } + /** + * Get signature part as json string + * \return signature part after base64 decoding + */ + const typename json_traits::string_type& get_signature() const noexcept { return signature; } + /** + * Get header part as base64 string + * \return header part before base64 decoding + */ + const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } + /** + * Get payload part as base64 string + * \return payload part before base64 decoding + */ + const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } + /** + * Get signature part as base64 string + * \return signature part before base64 decoding + */ + const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } + /** + * Get all payload claims + * \return map of claims + */ + std::unordered_map get_payload_claims() const { + return this->payload_claims.get_claims(); + } + /** + * Get all header claims + * \return map of claims + */ + std::unordered_map get_header_claims() const { + return this->header_claims.get_claims(); + } + }; + + /** + * Builder class to build and sign a new token + * Use jwt::create() to get an instance of this class. + */ + template + class builder { + typename json_traits::object_type header_claims; + typename json_traits::object_type payload_claims; + + public: + builder() = default; + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + header_claims[id] = std::move(c); + return *this; + } + + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { + header_claims[id] = c.to_json(); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + payload_claims[id] = std::move(c); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { + payload_claims[id] = c.to_json(); + return *this; + } + /** + * Set algorithm claim + * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * \param str Name of algorithm + * \return *this to allow for method chaining + */ + builder& set_algorithm(typename json_traits::string_type str) { + return set_header_claim("alg", typename json_traits::value_type(str)); + } + /** + * Set type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_type(typename json_traits::string_type str) { + return set_header_claim("typ", typename json_traits::value_type(str)); + } + /** + * Set content type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_content_type(typename json_traits::string_type str) { + return set_header_claim("cty", typename json_traits::value_type(str)); + } + /** + * Set key id claim + * \param str Key id to set + * \return *this to allow for method chaining + */ + builder& set_key_id(typename json_traits::string_type str) { + return set_header_claim("kid", typename json_traits::value_type(str)); + } + /** + * Set issuer claim + * \param str Issuer to set + * \return *this to allow for method chaining + */ + builder& set_issuer(typename json_traits::string_type str) { + return set_payload_claim("iss", typename json_traits::value_type(str)); + } + /** + * Set subject claim + * \param str Subject to set + * \return *this to allow for method chaining + */ + builder& set_subject(typename json_traits::string_type str) { + return set_payload_claim("sub", typename json_traits::value_type(str)); + } + /** + * Set audience claim + * \param a Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::array_type a) { + return set_payload_claim("aud", typename json_traits::value_type(a)); + } + /** + * Set audience claim + * \param aud Single audience + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::string_type aud) { + return set_payload_claim("aud", typename json_traits::value_type(aud)); + } + /** + * Set expires at claim + * \param d Expires time + * \return *this to allow for method chaining + */ + builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } + /** + * Set not before claim + * \param d First valid time + * \return *this to allow for method chaining + */ + builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } + /** + * Set issued at claim + * \param d Issued at time, should be current time + * \return *this to allow for method chaining + */ + builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } + /** + * Set id claim + * \param str ID to set + * \return *this to allow for method chaining + */ + builder& set_id(const typename json_traits::string_type& str) { + return set_payload_claim("jti", typename json_traits::value_type(str)); + } + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode) const { + std::error_code ec; + auto res = sign(algo, encode, ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo) const { + std::error_code ec; + auto res = sign(algo, ec); + error::throw_if_error(ec); + return res; + } +#endif + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \param ec error_code filled with details on error + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { + // make a copy such that a builder can be re-used + typename json_traits::object_type obj_header = header_claims; + if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); + + const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); + const auto token = header + "." + payload; + + auto signature = algo.sign(token, ec); + if (ec) return {}; + + return token + "." + encode(signature); + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \param ec error_code filled with details on error + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { + return sign( + algo, + [](const typename json_traits::string_type& data) { + return base::trim(base::encode(data)); + }, + ec); + } +#endif + }; + + /** + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid + * signature. + */ + template + class verifier { + struct algo_base { + virtual ~algo_base() = default; + virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; + }; + template + struct algo : public algo_base { + T alg; + explicit algo(T a) : alg(a) {} + void verify(const std::string& data, const std::string& sig, std::error_code& ec) override { + alg.verify(data, sig, ec); + } + }; + + using basic_claim_t = basic_claim; + /// Required claims + std::unordered_map claims; + /// Leeway time for exp, nbf and iat + size_t default_leeway = 0; + /// Instance of clock type + Clock clock; + /// Supported algorithms + std::unordered_map> algs; + + public: + /** + * Constructor for building a new verifier instance + * \param c Clock instance + */ + explicit verifier(Clock c) : clock(c) {} + + /** + * Set default leeway to use. + * \param leeway Default leeway to use if not specified otherwise + * \return *this to allow chaining + */ + verifier& leeway(size_t leeway) { + default_leeway = leeway; + return *this; + } + /** + * Set leeway for expires at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for expires at. + * \return *this to allow chaining + */ + verifier& expires_at_leeway(size_t leeway) { + return with_claim("exp", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set leeway for not before. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for not before. + * \return *this to allow chaining + */ + verifier& not_before_leeway(size_t leeway) { + return with_claim("nbf", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set leeway for issued at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for issued at. + * \return *this to allow chaining + */ + verifier& issued_at_leeway(size_t leeway) { + return with_claim("iat", basic_claim_t(std::chrono::system_clock::from_time_t(leeway))); + } + /** + * Set an issuer to check for. + * Check is casesensitive. + * \param iss Issuer to check for. + * \return *this to allow chaining + */ + verifier& with_issuer(const typename json_traits::string_type& iss) { + return with_claim("iss", basic_claim_t(iss)); + } + /** + * Set a subject to check for. + * Check is casesensitive. + * \param sub Subject to check for. + * \return *this to allow chaining + */ + verifier& with_subject(const typename json_traits::string_type& sub) { + return with_claim("sub", basic_claim_t(sub)); + } + /** + * Set an audience to check for. + * If any of the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename basic_claim_t::set_t& aud) { + return with_claim("aud", basic_claim_t(aud)); + } + /** + * Set an audience to check for. + * If the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename json_traits::string_type& aud) { + return with_claim("aud", basic_claim_t(aud)); + } + /** + * Set an id to check for. + * Check is casesensitive. + * \param id ID to check for. + * \return *this to allow chaining + */ + verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } + /** + * Specify a claim to check for. + * \param name Name of the claim to check for + * \param c Claim to check for + * \return *this to allow chaining + */ + verifier& with_claim(const typename json_traits::string_type& name, basic_claim_t c) { + claims[name] = c; + return *this; + } + + /** + * Add an algorithm available for checking. + * \param alg Algorithm to allow + * \return *this to allow chaining + */ + template + verifier& allow_algorithm(Algorithm alg) { + algs[alg.name()] = std::make_shared>(alg); + return *this; + } + + /** + * Verify the given token. + * \param jwt Token to check + * \throw token_verification_exception Verification failed + */ + void verify(const decoded_jwt& jwt) const { + std::error_code ec; + verify(jwt, ec); + error::throw_if_error(ec); + } + /** + * Verify the given token. + * \param jwt Token to check + * \param ec error_code filled with details on error + */ + void verify(const decoded_jwt& jwt, std::error_code& ec) const { + ec.clear(); + const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const typename json_traits::string_type sig = jwt.get_signature(); + const std::string algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + algs.at(algo)->verify(data, sig, ec); + if (ec) return; + + auto assert_claim_eq = [](const decoded_jwt& jwt, const typename json_traits::string_type& key, + const basic_claim_t& c, std::error_code& ec) { + if (!jwt.has_payload_claim(key)) { + ec = error::token_verification_error::missing_claim; + return; + } + auto jc = jwt.get_payload_claim(key); + if (jc.get_type() != c.get_type()) { + ec = error::token_verification_error::claim_type_missmatch; + return; + } + if (c.get_type() == json::type::integer) { + if (c.as_date() != jc.as_date()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else if (c.get_type() == json::type::array) { + auto s1 = c.as_set(); + auto s2 = jc.as_set(); + if (s1.size() != s2.size()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + auto it1 = s1.cbegin(); + auto it2 = s2.cbegin(); + while (it1 != s1.cend() && it2 != s2.cend()) { + if (*it1++ != *it2++) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } + } else if (c.get_type() == json::type::object) { + if (json_traits::serialize(c.to_json()) != json_traits::serialize(jc.to_json())) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else if (c.get_type() == json::type::string) { + if (c.as_string() != jc.as_string()) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } else + throw std::logic_error("internal error, should be unreachable"); + }; + + auto time = clock.now(); + + if (jwt.has_expires_at()) { + auto leeway = claims.count("exp") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) + : default_leeway; + auto exp = jwt.get_expires_at(); + if (time > exp + std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + if (jwt.has_issued_at()) { + auto leeway = claims.count("iat") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) + : default_leeway; + auto iat = jwt.get_issued_at(); + if (time < iat - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + if (jwt.has_not_before()) { + auto leeway = claims.count("nbf") == 1 + ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) + : default_leeway; + auto nbf = jwt.get_not_before(); + if (time < nbf - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + return; + } + } + for (auto& c : claims) { + if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { + // Nothing to do here, already checked + } else if (c.first == "aud") { + if (!jwt.has_audience()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + auto aud = jwt.get_audience(); + typename basic_claim_t::set_t expected = {}; + if (c.second.get_type() == json::type::string) + expected = {c.second.as_string()}; + else + expected = c.second.as_set(); + for (auto& e : expected) { + if (aud.count(e) == 0) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } + } else { + assert_claim_eq(jwt, c.first, c.second, ec); + if (ec) return; + } + } + } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(Clock c) { + return verifier(c); + } + + /** + * Default clock class using std::chrono::system_clock as a backend. + */ + struct default_clock { + date now() const { return date::clock::now(); } + }; + + /** + * Return a builder instance to create a new token + */ + template + builder create() { + return builder(); + } + + /** + * Decode a token + * \param token Token to decode + * \param decode function that will pad and base64url decode the token + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token) { + return decoded_jwt(token); + } + +#ifndef JWT_DISABLE_PICOJSON + struct picojson_traits { + using value_type = picojson::value; + using object_type = picojson::object; + using array_type = picojson::array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static json::type get_type(const picojson::value& val) { + using json::type; + if (val.is()) return type::boolean; + if (val.is()) return type::integer; + if (val.is()) return type::number; + if (val.is()) return type::string; + if (val.is()) return type::array; + if (val.is()) return type::object; + + throw std::logic_error("invalid type"); + } + + static picojson::object as_object(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static picojson::array as_array(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_int(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool as_bool(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool parse(picojson::value& val, const std::string& str) { return picojson::parse(val, str).empty(); } + + static std::string serialize(const picojson::value& val) { return val.serialize(); } + }; + + /** + * Default JSON claim + * + * This type is the default specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + /** + * Return a picojson builder instance to create a new token + */ + inline builder create() { return builder(); } +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { return decoded_jwt(token); } +#endif + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } +#endif +} // namespace jwt + +template +std::istream& operator>>(std::istream& is, jwt::basic_claim& c) { + return c.operator>>(is); +} + +template +std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& c) { + return os << c.to_json(); +} + +#endif diff --git a/dep/jwt-cpp/include/nlohmann/json.hpp b/dep/jwt-cpp/include/nlohmann/json.hpp new file mode 100644 index 000000000..a70aaf8cb --- /dev/null +++ b/dep/jwt-cpp/include/nlohmann/json.hpp @@ -0,0 +1,25447 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.9.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 9 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // unique_ptr +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include + + +#include + +// #include + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +// #include + + +#include // size_t + +namespace nlohmann +{ +namespace detail +{ +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // pair +// #include +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * For details, see . + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 13 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(_MSC_VER) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP \ +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow to override assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + JSON_HEDLEY_RETURNS_NON_NULL + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + JSON_HEDLEY_NON_NULL(3) + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. +json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). +json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] pos the position where the error occurred (or with + chars_read_total=0 if the position cannot be + determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, const position_t& pos, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + position_string(pos) + ": " + what_arg; + return parse_error(id_, pos.chars_read_total, w.c_str()); + } + + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} + + static std::string position_string(const position_t& pos) + { + return " at line " + std::to_string(pos.lines_read + 1) + + ", column " + std::to_string(pos.chars_read_current_line); + } +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | +json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | +json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include + +// #include + + +// https://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; +} // namespace detail +} // namespace nlohmann + +// #include +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ +#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; + +template +struct ordered_map; + +/*! +@brief ordered JSON class + +This type preserves the insertion order of object keys. + +@since version 3.9.0 +*/ +using ordered_json = basic_json; + +} // namespace nlohmann + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using iterator_t = typename T::iterator; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, + enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + + +/////////////////// +// is_ functions // +/////////////////// + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + std::is_constructible::value && + std::is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (std::is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (std::is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type_impl : std::false_type {}; + +template +struct is_compatible_string_type_impl < + BasicJsonType, CompatibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_compatible_string_type + : is_compatible_string_type_impl {}; + +template +struct is_constructible_string_type_impl : std::false_type {}; + +template +struct is_constructible_string_type_impl < + BasicJsonType, ConstructibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_constructible_string_type + : is_constructible_string_type_impl {}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_detected::value&& + is_detected::value&& +// This is needed because json_reverse_iterator has a ::iterator type... +// Therefore it is detected as a CompatibleArrayType. +// The real fix would be to have an Iterable concept. + !is_iterator_traits < + iterator_traits>::value >> +{ + static constexpr bool value = + std::is_constructible::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + std::is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_detected::value&& +is_complete_type < +detected_t>::value >> +{ + static constexpr bool value = + // This is needed because json_reverse_iterator has a ::iterator type, + // furthermore, std::back_insert_iterator (and other iterators) have a + // base class `iterator`... Therefore it is detected as a + // ConstructibleArrayType. The real fix would be to have an Iterable + // concept. + !is_iterator_traits>::value && + + (std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, typename ConstructibleArrayType::value_type >::value); +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B1 { }; +template +struct conjunction +: std::conditional, B1>::type {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +} +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +template +void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_null())) + { + JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); + } + n = nullptr; +} + +// overloads for basic_json template parameters +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < std::is_arithmetic::value&& + !std::is_same::value, + int > = 0 > +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template < + typename BasicJsonType, typename ConstructibleStringType, + enable_if_t < + is_constructible_string_type::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ConstructibleStringType& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.clear(); + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::transform(j.begin(), j.end(), std::begin(l), + [](const BasicJsonType & elem) + { + return elem.template get(); + }); +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N]) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +{ + arr = *j.template get_ptr(); +} + +template +auto from_json_array_impl(const BasicJsonType& j, std::array& arr, + priority_tag<2> /*unused*/) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + j.template get(), + void()) +{ + using std::end; + + ConstructibleArrayType ret; + ret.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(ret, end(ret)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template +void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) +{ + using std::end; + + ConstructibleArrayType ret; + std::transform( + j.begin(), j.end(), std::inserter(ret, end(ret)), + [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template < typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t < + is_constructible_array_type::value&& + !is_constructible_object_type::value&& + !is_constructible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) +-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), +j.template get(), +void()) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<3> {}); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); + } + + bin = *j.template get_ptr(); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + ConstructibleObjectType ret; + auto inner_object = j.template get_ptr(); + using value_type = typename ConstructibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(ret, ret.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); + obj = std::move(ret); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::unordered_map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +struct from_json_fn +{ + template + auto operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } +}; +} // namespace detail + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} // namespace +} // namespace nlohmann + +// #include + + +#include // copy +#include // begin, end +#include // string +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + + +#include // size_t +#include // input_iterator_tag +#include // string, to_string +#include // tuple_size, get, tuple_element + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +void int_to_string( string_type& target, std::size_t value ) +{ + // For ADL + using std::to_string; + target = to_string(value); +} +template class iteration_proxy_value +{ + public: + using difference_type = std::ptrdiff_t; + using value_type = iteration_proxy_value; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::input_iterator_tag; + using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; + + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + /// last stringified array index + mutable std::size_t array_index_last = 0; + /// a string representation of the array index + mutable string_type array_index_str = "0"; + /// an empty string (to return a reference for primitive values) + const string_type empty_str = ""; + + public: + explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_value& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_value& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// equality operator (needed for InputIterator) + bool operator==(const iteration_proxy_value& o) const + { + return anchor == o.anchor; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_value& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + const string_type& key() const + { + JSON_ASSERT(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + if (array_index != array_index_last) + { + int_to_string( array_index_str, array_index ); + array_index_last = array_index; + } + return array_index_str; + } + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return empty_str; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } +}; + +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_value begin() noexcept + { + return iteration_proxy_value(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_value end() noexcept + { + return iteration_proxy_value(container.end()); + } +}; +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) +{ + return i.key(); +} +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) +{ + return i.value(); +} +} // namespace detail +} // namespace nlohmann + +// The Addition to the STD Namespace is required to add +// Structured Bindings Support to the iteration_proxy_value class +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +namespace std +{ +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::iteration_proxy_value> + : public std::integral_constant {}; + +template +class tuple_element> +{ + public: + using type = decltype( + get(std::declval < + ::nlohmann::detail::iteration_proxy_value> ())); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif +} // namespace std + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleStringType, + enable_if_t < !std::is_same::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleStringType& str) + { + j.m_type = value_t::string; + j.m_value.string = j.template create(str); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_type = value_t::binary; + typename BasicJsonType::binary_t value{b}; + j.m_value = value; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_type = value_t::binary; + typename BasicJsonType::binary_t value{std::move(b)}; + j.m_value = value; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < !std::is_same::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + if (arr.size() > 0) + { + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + } + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < !std::is_same::value, int > = 0 > + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_array_type::value&& + !is_compatible_object_type::value&& + !is_compatible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +{ + external_constructor::construct(j, bin); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::valarray& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template < + typename BasicJsonType, typename T, std::size_t N, + enable_if_t < !std::is_constructible::value, + int > = 0 > +void to_json(BasicJsonType& j, const T(&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = { p.first, p.second }; +} + +// for https://github.com/nlohmann/json/pull/1134 +template>::value, int> = 0> +void to_json(BasicJsonType& j, const T& b) +{ + j = { {b.key(), b.value()} }; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +{ + j = { std::get(t)... }; +} + +template::value, int > = 0> +void to_json(BasicJsonType& j, const T& t) +{ + to_json_tuple_impl(j, t, make_index_sequence::value> {}); +} + +struct to_json_fn +{ + template + auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } +}; +} // namespace detail + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} // namespace +} // namespace nlohmann + + +namespace nlohmann +{ + +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + -> decltype(::nlohmann::from_json(std::forward(j), val), void()) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; + +} // namespace nlohmann + +// #include + + +#include // uint8_t +#include // tie +#include // move + +namespace nlohmann +{ + +/*! +@brief an internal type for a backed binary type + +This type extends the template parameter @a BinaryType provided to `basic_json` +with a subtype used by BSON and MessagePack. This type exists so that the user +does not have to specify a type themselves with a specific naming scheme in +order to override the binary type. + +@tparam BinaryType container to store bytes (`std::vector` by + default) + +@since version 3.8.0 +*/ +template +class byte_container_with_subtype : public BinaryType +{ + public: + /// the type of the underlying container + using container_type = BinaryType; + + byte_container_with_subtype() noexcept(noexcept(container_type())) + : container_type() + {} + + byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) + : container_type(b) + {} + + byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + {} + + byte_container_with_subtype(const container_type& b, std::uint8_t subtype) noexcept(noexcept(container_type(b))) + : container_type(b) + , m_subtype(subtype) + , m_has_subtype(true) + {} + + byte_container_with_subtype(container_type&& b, std::uint8_t subtype) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + , m_subtype(subtype) + , m_has_subtype(true) + {} + + bool operator==(const byte_container_with_subtype& rhs) const + { + return std::tie(static_cast(*this), m_subtype, m_has_subtype) == + std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); + } + + bool operator!=(const byte_container_with_subtype& rhs) const + { + return !(rhs == *this); + } + + /*! + @brief sets the binary subtype + + Sets the binary subtype of the value, also flags a binary JSON value as + having a subtype, which has implications for serialization. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void set_subtype(std::uint8_t subtype) noexcept + { + m_subtype = subtype; + m_has_subtype = true; + } + + /*! + @brief return the binary subtype + + Returns the numerical subtype of the value if it has a subtype. If it does + not have a subtype, this function will return size_t(-1) as a sentinel + value. + + @return the numerical subtype of the binary value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + constexpr std::uint8_t subtype() const noexcept + { + return m_subtype; + } + + /*! + @brief return whether the value has a subtype + + @return whether the value has a subtype + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref clear_subtype() -- clears the binary subtype + + @since version 3.8.0 + */ + constexpr bool has_subtype() const noexcept + { + return m_has_subtype; + } + + /*! + @brief clears the binary subtype + + Clears the binary subtype and flags the value as not having a subtype, which + has implications for serialization; for instance MessagePack will prefer the + bin family over the ext family. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa @ref subtype() -- return the binary subtype + @sa @ref set_subtype() -- sets the binary subtype + @sa @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void clear_subtype() noexcept + { + m_subtype = 0; + m_has_subtype = false; + } + + private: + std::uint8_t m_subtype = 0; + bool m_has_subtype = false; +}; + +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +#include // size_t, uint8_t +#include // hash + +namespace nlohmann +{ +namespace detail +{ + +// boost::hash_combine +inline std::size_t combine(std::size_t seed, std::size_t h) noexcept +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ +template +std::size_t hash(const BasicJsonType& j) +{ + using string_t = typename BasicJsonType::string_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + const auto type = static_cast(j.type()); + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::value_t::discarded: + { + return combine(type, 0); + } + + case BasicJsonType::value_t::object: + { + auto seed = combine(type, j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(type, j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash {}(j.template get_ref()); + return combine(type, h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::number_unsigned: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::number_float: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case nlohmann::detail::value_t::binary: + { + auto seed = combine(type, j.get_binary().size()); + const auto h = std::hash {}(j.get_binary().has_subtype()); + seed = combine(seed, h); + seed = combine(seed, j.get_binary().subtype()); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash {}(byte)); + } + return seed; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } +} + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // generate_n +#include // array +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // snprintf +#include // memcpy +#include // back_inserter +#include // numeric_limits +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + + +#include // array +#include // size_t +#include //FILE * +#include // strlen +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// the supported input formats +enum class input_format_t { json, cbor, msgpack, ubjson, bson }; + +//////////////////// +// input adapters // +//////////////////// + +/*! +Input adapter for stdio file access. This adapter read only 1 byte and do not use any + buffer. This adapter is a very low level adapter. +*/ +class file_input_adapter +{ + public: + using char_type = char; + + JSON_HEDLEY_NON_NULL(2) + explicit file_input_adapter(std::FILE* f) noexcept + : m_file(f) + {} + + // make class move-only + file_input_adapter(const file_input_adapter&) = delete; + file_input_adapter(file_input_adapter&&) = default; + file_input_adapter& operator=(const file_input_adapter&) = delete; + file_input_adapter& operator=(file_input_adapter&&) = delete; + + std::char_traits::int_type get_character() noexcept + { + return std::fgetc(m_file); + } + + private: + /// the file pointer to read from + std::FILE* m_file; +}; + + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter +{ + public: + using char_type = char; + + ~input_stream_adapter() + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags, except eof + if (is != nullptr) + { + is->clear(is->rdstate() & std::ios::eofbit); + } + } + + explicit input_stream_adapter(std::istream& i) + : is(&i), sb(i.rdbuf()) + {} + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&& rhs) = delete; + + input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) + { + rhs.is = nullptr; + rhs.sb = nullptr; + } + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() + { + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == EOF)) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + + private: + /// the associated input stream + std::istream* is = nullptr; + std::streambuf* sb = nullptr; +}; + +// General-purpose iterator-based adapter. It might not be as fast as +// theoretically possible for some containers, but it is extremely versatile. +template +class iterator_input_adapter +{ + public: + using char_type = typename std::iterator_traits::value_type; + + iterator_input_adapter(IteratorType first, IteratorType last) + : current(std::move(first)), end(std::move(last)) {} + + typename std::char_traits::int_type get_character() + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + auto result = std::char_traits::to_int_type(*current); + std::advance(current, 1); + return result; + } + else + { + return std::char_traits::eof(); + } + } + + private: + IteratorType current; + IteratorType end; + + template + friend struct wide_string_input_helper; + + bool empty() const + { + return current == end; + } + +}; + + +template +struct wide_string_input_helper; + +template +struct wide_string_input_helper +{ + // UTF-32 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-32 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (wc <= 0xFFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else if (wc <= 0x10FFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + // unknown character + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } +}; + +template +struct wide_string_input_helper +{ + // UTF-16 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-16 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (0xD800 > wc || wc >= 0xE000) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else + { + if (JSON_HEDLEY_UNLIKELY(!input.empty())) + { + const auto wc2 = static_cast(input.get_character()); + const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); + utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } + } +}; + +// Wraps another input apdater to convert wide character types into individual bytes. +template +class wide_string_input_adapter +{ + public: + using char_type = char; + + wide_string_input_adapter(BaseInputAdapter base) + : base_adapter(base) {} + + typename std::char_traits::int_type get_character() noexcept + { + // check if buffer needs to be filled + if (utf8_bytes_index == utf8_bytes_filled) + { + fill_buffer(); + + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index == 0); + } + + // use buffer + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); + return utf8_bytes[utf8_bytes_index++]; + } + + private: + BaseInputAdapter base_adapter; + + template + void fill_buffer() + { + wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); + } + + /// a buffer for UTF-8 bytes + std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; + + /// index to the utf8_codes array for the next valid byte + std::size_t utf8_bytes_index = 0; + /// number of valid bytes in the utf8_codes array + std::size_t utf8_bytes_filled = 0; +}; + + +template +struct iterator_input_adapter_factory +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(std::move(first), std::move(last)); + } +}; + +template +struct is_iterator_of_multibyte +{ + using value_type = typename std::iterator_traits::value_type; + enum + { + value = sizeof(value_type) > 1 + }; +}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using base_adapter_type = iterator_input_adapter; + using adapter_type = wide_string_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(base_adapter_type(std::move(first), std::move(last))); + } +}; + +// General purpose iterator-based input +template +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +{ + using factory_type = iterator_input_adapter_factory; + return factory_type::create(first, last); +} + +// Convenience shorthand from container to iterator +template +auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container))) +{ + // Enable ADL + using std::begin; + using std::end; + + return input_adapter(begin(container), end(container)); +} + +// Special cases with fast paths +inline file_input_adapter input_adapter(std::FILE* file) +{ + return file_input_adapter(file); +} + +inline input_stream_adapter input_adapter(std::istream& stream) +{ + return input_stream_adapter(stream); +} + +inline input_stream_adapter input_adapter(std::istream&& stream) +{ + return input_stream_adapter(stream); +} + +using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); + +// Null-delimited strings, and the like. +template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + !std::is_array::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > +contiguous_bytes_input_adapter input_adapter(CharT b) +{ + auto length = std::strlen(reinterpret_cast(b)); + const auto* ptr = reinterpret_cast(b); + return input_adapter(ptr, ptr + length); +} + +template +auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) +{ + return input_adapter(array, array + N); +} + +// This class only handles inputs of input_buffer_adapter type. +// It's required so that expressions like {ptr, len} can be implicitely casted +// to the correct adapter. +class span_input_adapter +{ + public: + template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > + span_input_adapter(CharT b, std::size_t l) + : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} + + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + span_input_adapter(IteratorType first, IteratorType last) + : ia(input_adapter(first, last)) {} + + contiguous_bytes_input_adapter&& get() + { + return std::move(ia); + } + + private: + contiguous_bytes_input_adapter ia; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include +#include // string +#include // move +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief an floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary string was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + virtual ~json_sax() = default; +}; + + +namespace detail +{ +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @param[in, out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) + : root(r), allow_exceptions(allow_exceptions_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(len))); + } + + return true; + } + + bool key(string_t& val) + { + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(len))); + } + + return true; + } + + bool end_array() + { + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->emplace_back(std::forward(v)); + return &(ref_stack.back()->m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + + json_sax_dom_callback_parser(BasicJsonType& r, + const parser_callback_t cb, + const bool allow_exceptions_ = true) + : root(r), callback(cb), allow_exceptions(allow_exceptions_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + // check object limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); + } + + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back() && !callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + // check array limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (!keep) + { + // discard array + *ref_stack.back() = discarded; + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->pop_back(); + } + + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template + std::pair handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward(v)); + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, &root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->push_back(std::move(value)); + return {true, &(ref_stack.back()->m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; +}; + +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; +} // namespace detail + +} // namespace nlohmann + +// #include + + +#include // array +#include // localeconv +#include // size_t +#include // snprintf +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // char_traits, string +#include // move +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +template +class lexer_base +{ + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_unsigned: + case token_type::value_integer: + case token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } +}; +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer : public lexer_base +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + using token_type = typename lexer_base::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) + {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; + ~lexer() = default; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; + + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast((static_cast(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast((static_cast(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast((static_cast(current) - 0x57u) << factor); + } + else + { + return -1; + } + } + + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast( + // high surrogate occupies the most significant 22 bits + (static_cast(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast(codepoint2) + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } + + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } + + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } + + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } + + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } + + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } + + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } + + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } + + case 0x08: + { + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; + } + + case 0x09: + { + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; + } + + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } + + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } + + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } + + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } + + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } + + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } + + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } + + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } + + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } + + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } + + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } + + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } + + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } + + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } + + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } + + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } + + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } + + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } + + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } + + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } + + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } + + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case std::char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case std::char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) + { + JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + char_int_type get() + { + ++position.chars_read_total; + ++position.chars_read_current_line; + + if (next_unget) + { + // just reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } + + return current; + } + + /*! + @brief unget current character (read it again on next get) + + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. + */ + void unget() + { + next_unget = true; + + --position.chars_read_total; + + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) + { + if (position.lines_read > 0) + { + --position.lines_read; + } + } + else + { + --position.chars_read_current_line; + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + return token_buffer; + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast(c) <= '\x1F') + { + // escape control characters + std::array cs{{}}; + (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); + result += cs.data(); + } + else + { + // add character as is + result.push_back(static_cast(c)); + } + } + + return result; + } + + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } + + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); + return true; + } + + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } + + token_type scan() + { + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } + + // read next character and ignore whitespace + skip_whitespace(); + + // ignore comments + while (ignore_comments && current == '/') + { + if (!scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); + } + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + { + std::array true_literal = {{'t', 'r', 'u', 'e'}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array false_literal = {{'f', 'a', 'l', 's', 'e'}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array null_literal = {{'n', 'u', 'l', 'l'}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + InputAdapterType ia; + + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// whether the next get() call should just return current + bool next_unget = false; + + /// the start position of the current token + position_t position {}; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char_int_type decimal_point_char = '.'; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // size_t +#include // declval +#include // string + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +using null_function_t = decltype(std::declval().null()); + +template +using boolean_function_t = + decltype(std::declval().boolean(std::declval())); + +template +using number_integer_function_t = + decltype(std::declval().number_integer(std::declval())); + +template +using number_unsigned_function_t = + decltype(std::declval().number_unsigned(std::declval())); + +template +using number_float_function_t = decltype(std::declval().number_float( + std::declval(), std::declval())); + +template +using string_function_t = + decltype(std::declval().string(std::declval())); + +template +using binary_function_t = + decltype(std::declval().binary(std::declval())); + +template +using start_object_function_t = + decltype(std::declval().start_object(std::declval())); + +template +using key_function_t = + decltype(std::declval().key(std::declval())); + +template +using end_object_function_t = decltype(std::declval().end_object()); + +template +using start_array_function_t = + decltype(std::declval().start_array(std::declval())); + +template +using end_array_function_t = decltype(std::declval().end_array()); + +template +using parse_error_function_t = decltype(std::declval().parse_error( + std::declval(), std::declval(), + std::declval())); + +template +struct is_sax +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static constexpr bool value = + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value; +}; + +template +struct is_sax_static_asserts +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static_assert(is_detected_exact::value, + "Missing/invalid function: bool null()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_integer(number_integer_t)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool string(string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool binary(binary_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_object(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool key(string_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_object()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_array(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_array()"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool parse_error(std::size_t, const " + "std::string&, const exception&)"); +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ + +/// how to treat CBOR tags +enum class cbor_tag_handler_t +{ + error, ///< throw a parse_error exception in case of a tag + ignore ///< ignore tags +}; + +/*! +@brief determine system byte order + +@return true if and only if system's byte order is little endian + +@note from https://stackoverflow.com/a/1001328/266378 +*/ +static inline bool little_endianess(int num = 1) noexcept +{ + return *reinterpret_cast(&num) == 1; +} + + +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR, MessagePack, and UBJSON values +*/ +template> +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using json_sax_t = SAX; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(InputAdapterType&& adapter) : ia(std::move(adapter)) + { + (void)detail::is_sax_static_asserts {}; + } + + // make class move-only + binary_reader(const binary_reader&) = delete; + binary_reader(binary_reader&&) = default; + binary_reader& operator=(const binary_reader&) = delete; + binary_reader& operator=(binary_reader&&) = default; + ~binary_reader() = default; + + /*! + @param[in] format the binary format to parse + @param[in] sax_ a SAX event processor + @param[in] strict whether to expect the input to be consumed completed + @param[in] tag_handler how to treat CBOR tags + + @return + */ + JSON_HEDLEY_NON_NULL(3) + bool sax_parse(const input_format_t format, + json_sax_t* sax_, + const bool strict = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + sax = sax_; + bool result = false; + + switch (format) + { + case input_format_t::bson: + result = parse_bson_internal(); + break; + + case input_format_t::cbor: + result = parse_cbor_internal(true, tag_handler); + break; + + case input_format_t::msgpack: + result = parse_msgpack_internal(); + break; + + case input_format_t::ubjson: + result = parse_ubjson_internal(); + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + + // strict mode: next byte must be EOF + if (result && strict) + { + if (format == input_format_t::ubjson) + { + get_ignore_noop(); + } + else + { + get(); + } + + if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) + { + return sax->parse_error(chars_read, get_token_string(), + parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); + } + } + + return result; + } + + private: + ////////// + // BSON // + ////////// + + /*! + @brief Reads in a BSON-object and passes it to the SAX-parser. + @return whether a valid BSON-value was passed to the SAX parser + */ + bool parse_bson_internal() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) + { + return false; + } + + return sax->end_object(); + } + + /*! + @brief Parses a C-style string from the BSON input. + @param[in, out] result A reference to the string variable where the read + string is to be stored. + @return `true` if the \x00-byte indicating the end of the string was + encountered before the EOF; false` indicates an unexpected EOF. + */ + bool get_bson_cstr(string_t& result) + { + auto out = std::back_inserter(result); + while (true) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) + { + return false; + } + if (current == 0x00) + { + return true; + } + *out++ = static_cast(current); + } + } + + /*! + @brief Parses a zero-terminated string of length @a len from the BSON + input. + @param[in] len The length (including the zero-byte at the end) of the + string to be read. + @param[in, out] result A reference to the string variable where the read + string is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 1 + @return `true` if the string was successfully parsed + */ + template + bool get_bson_string(const NumberType len, string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 1)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); + } + + return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); + } + + /*! + @brief Parses a byte array input of length @a len from the BSON input. + @param[in] len The length of the byte array to be read. + @param[in, out] result A reference to the binary variable where the read + array is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 0 + @return `true` if the byte array was successfully parsed + */ + template + bool get_bson_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); + } + + // All BSON binary values have a subtype + std::uint8_t subtype{}; + get_number(input_format_t::bson, subtype); + result.set_subtype(subtype); + + return get_binary(input_format_t::bson, len, result); + } + + /*! + @brief Read a BSON document element of the given @a element_type. + @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html + @param[in] element_type_parse_position The position in the input stream, + where the `element_type` was read. + @warning Not all BSON element types are supported yet. An unsupported + @a element_type will give rise to a parse_error.114: + Unsupported BSON record type 0x... + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_internal(const char_int_type element_type, + const std::size_t element_type_parse_position) + { + switch (element_type) + { + case 0x01: // double + { + double number{}; + return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); + } + + case 0x02: // string + { + std::int32_t len{}; + string_t value; + return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); + } + + case 0x03: // object + { + return parse_bson_internal(); + } + + case 0x04: // array + { + return parse_bson_array(); + } + + case 0x05: // binary + { + std::int32_t len{}; + binary_t value; + return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); + } + + case 0x08: // boolean + { + return sax->boolean(get() != 0); + } + + case 0x0A: // null + { + return sax->null(); + } + + case 0x10: // int32 + { + std::int32_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + case 0x12: // int64 + { + std::int64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + default: // anything else not supported (yet) + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); + return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); + } + } + } + + /*! + @brief Read a BSON element list (as specified in the BSON-spec) + + The same binary layout is used for objects and arrays, hence it must be + indicated with the argument @a is_array which one is expected + (true --> array, false --> object). + + @param[in] is_array Determines if the element list being read is to be + treated as an object (@a is_array == false), or as an + array (@a is_array == true). + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_list(const bool is_array) + { + string_t key; + + while (auto element_type = get()) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) + { + return false; + } + + const std::size_t element_type_parse_position = chars_read; + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + { + return false; + } + + if (!is_array && !sax->key(key)) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) + { + return false; + } + + // get_bson_cstr only appends + key.clear(); + } + + return true; + } + + /*! + @brief Reads an array from the BSON input and passes it to the SAX-parser. + @return whether a valid BSON-array was passed to the SAX parser + */ + bool parse_bson_array() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) + { + return false; + } + + return sax->end_array(); + } + + ////////// + // CBOR // + ////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true) or whether the last read character should + be considered instead (false) + @param[in] tag_handler how CBOR tags should be treated + + @return whether a valid CBOR value was passed to the SAX parser + */ + bool parse_cbor_internal(const bool get_char, + const cbor_tag_handler_t tag_handler) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::cbor, "value"); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return sax->number_unsigned(static_cast(current)); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return sax->number_integer(static_cast(0x20 - 1 - current)); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) + - static_cast(number)); + } + + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) && sax->binary(b); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + string_t s; + return get_cbor_string(s) && sax->string(s); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0x98: // array (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9F: // array (indefinite length) + return get_cbor_array(std::size_t(-1), tag_handler); + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0xB8: // map (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBF: // map (indefinite length) + return get_cbor_object(std::size_t(-1), tag_handler); + + case 0xC6: // tagged item + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD8: // tagged item (1 bytes follow) + case 0xD9: // tagged item (2 bytes follow) + case 0xDA: // tagged item (4 bytes follow) + case 0xDB: // tagged item (8 bytes follow) + { + switch (tag_handler) + { + case cbor_tag_handler_t::error: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); + } + + case cbor_tag_handler_t::ignore: + { + switch (current) + { + case 0xD8: + { + std::uint8_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xD9: + { + std::uint16_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDA: + { + std::uint32_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDB: + { + std::uint64_t len{}; + get_number(input_format_t::cbor, len); + break; + } + default: + break; + } + return parse_cbor_internal(true, tag_handler); + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + + case 0xF4: // false + return sax->boolean(false); + + case 0xF5: // true + return sax->boolean(true); + + case 0xF6: // null + return sax->null(); + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte1 << 8u) + byte2); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + float number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + double number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + default: // anything else (0xFF is handled inside the other types) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_cbor_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) + { + return false; + } + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + while (get() != 0xFF) + { + string_t chunk; + if (!get_cbor_string(chunk)) + { + return false; + } + result.append(chunk); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); + } + } + } + + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + { + return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (!get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); + } + } + } + + /*! + @param[in] len the length of the array or std::size_t(-1) for an + array of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether array creation completed + */ + bool get_cbor_array(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) + { + return false; + } + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object or std::size_t(-1) for an + object of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether object creation completed + */ + bool get_cbor_object(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + + return sax->end_object(); + } + + ///////////// + // MsgPack // + ///////////// + + /*! + @return whether a valid MessagePack value was passed to the SAX parser + */ + bool parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::msgpack, "value"); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return sax->number_unsigned(static_cast(current)); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + { + string_t s; + return get_msgpack_string(s) && sax->string(s); + } + + case 0xC0: // nil + return sax->null(); + + case 0xC2: // false + return sax->boolean(false); + + case 0xC3: // true + return sax->boolean(true); + + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + case 0xC7: // ext 8 + case 0xC8: // ext 16 + case 0xC9: // ext 32 + case 0xD4: // fixext 1 + case 0xD5: // fixext 2 + case 0xD6: // fixext 4 + case 0xD7: // fixext 8 + case 0xD8: // fixext 16 + { + binary_t b; + return get_msgpack_binary(b) && sax->binary(b); + } + + case 0xCA: // float 32 + { + float number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCB: // float 64 + { + double number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCC: // uint 8 + { + std::uint8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCD: // uint 16 + { + std::uint16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCE: // uint 32 + { + std::uint32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCF: // uint 64 + { + std::uint64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xD0: // int 8 + { + std::int8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD1: // int 16 + { + std::int16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD2: // int 32 + { + std::int32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD3: // int 64 + { + std::int64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xDC: // array 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDD: // array 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDE: // map 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + case 0xDF: // map 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + // negative fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return sax->number_integer(static_cast(current)); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_msgpack_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) + { + return false; + } + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + } + + case 0xD9: // str 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDA: // str 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDB: // str 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); + } + } + } + + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + // helper function to set the subtype + auto assign_and_return_true = [&result](std::int8_t subtype) + { + result.set_subtype(static_cast(subtype)); + return true; + }; + + switch (current) + { + case 0xC4: // bin 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // bin 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // bin 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC7: // ext 8 + { + std::uint8_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC8: // ext 16 + { + std::uint16_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC9: // ext 32 + { + std::uint32_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xD4: // fixext 1 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 1, result) && + assign_and_return_true(subtype); + } + + case 0xD5: // fixext 2 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 2, result) && + assign_and_return_true(subtype); + } + + case 0xD6: // fixext 4 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 4, result) && + assign_and_return_true(subtype); + } + + case 0xD7: // fixext 8 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 8, result) && + assign_and_return_true(subtype); + } + + case 0xD8: // fixext 16 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 16, result) && + assign_and_return_true(subtype); + } + + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + /*! + @param[in] len the length of the array + @return whether array creation completed + */ + bool get_msgpack_array(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object + @return whether object creation completed + */ + bool get_msgpack_object(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + key.clear(); + } + + return sax->end_object(); + } + + //////////// + // UBJSON // + //////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether a valid UBJSON value was passed to the SAX parser + */ + bool parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[out] result created string + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether string creation completed + */ + bool get_ubjson_string(string_t& result, const bool get_char = true) + { + if (get_char) + { + get(); // TODO(niels): may we ignore N here? + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + + switch (current) + { + case 'U': + { + std::uint8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'i': + { + std::int8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'I': + { + std::int16_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'l': + { + std::int32_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'L': + { + std::int64_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + default: + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); + } + } + + /*! + @param[out] result determined size + @return whether size determination completed + */ + bool get_ubjson_size_value(std::size_t& result) + { + switch (get_ignore_noop()) + { + case 'U': + { + std::uint8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'i': + { + std::int8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'I': + { + std::int16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'l': + { + std::int32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'L': + { + std::int64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); + } + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @param[out] result pair of the size and the type + + @return whether pair creation completed + */ + bool get_ubjson_size_type(std::pair& result) + { + result.first = string_t::npos; // size + result.second = 0; // type + + get_ignore_noop(); + + if (current == '$') + { + result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) + { + return false; + } + + get_ignore_noop(); + if (JSON_HEDLEY_UNLIKELY(current != '#')) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); + } + + return get_ubjson_size_value(result.first); + } + + if (current == '#') + { + return get_ubjson_size_value(result.first); + } + + return true; + } + + /*! + @param prefix the previously read or set type prefix + @return whether value creation completed + */ + bool get_ubjson_value(const char_int_type prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + return unexpect_eof(input_format_t::ubjson, "value"); + + case 'T': // true + return sax->boolean(true); + case 'F': // false + return sax->boolean(false); + + case 'Z': // null + return sax->null(); + + case 'U': + { + std::uint8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); + } + + case 'i': + { + std::int8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'I': + { + std::int16_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'l': + { + std::int32_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'L': + { + std::int64_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'd': + { + float number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'D': + { + double number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'H': + { + return get_ubjson_high_precision_number(); + } + + case 'C': // char + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); + } + string_t s(1, static_cast(current)); + return sax->string(s); + } + + case 'S': // string + { + string_t s; + return get_ubjson_string(s) && sax->string(s); + } + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); + } + } + } + + /*! + @return whether array creation completed + */ + bool get_ubjson_array() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) + { + return false; + } + get_ignore_noop(); + } + } + + return sax->end_array(); + } + + /*! + @return whether object creation completed + */ + bool get_ubjson_object() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + string_t key; + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + key.clear(); + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + key.clear(); + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + while (current != '}') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + get_ignore_noop(); + key.clear(); + } + } + + return sax->end_object(); + } + + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + + bool get_ubjson_high_precision_number() + { + // get size of following number string + std::size_t size{}; + auto res = get_ubjson_size_value(size); + if (JSON_HEDLEY_UNLIKELY(!res)) + { + return res; + } + + // get number string + std::vector number_vector; + for (std::size_t i = 0; i < size; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) + { + return false; + } + number_vector.push_back(static_cast(current)); + } + + // parse number string + auto number_ia = detail::input_adapter(std::forward(number_vector)); + auto number_lexer = detail::lexer(std::move(number_ia), false); + const auto result_number = number_lexer.scan(); + const auto number_string = number_lexer.get_token_string(); + const auto result_remainder = number_lexer.scan(); + + using token_type = typename detail::lexer_base::token_type; + + if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) + { + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); + } + + switch (result_number) + { + case token_type::value_integer: + return sax->number_integer(number_lexer.get_number_integer()); + case token_type::value_unsigned: + return sax->number_unsigned(number_lexer.get_number_unsigned()); + case token_type::value_float: + return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); + default: + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); + } + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + char_int_type get() + { + ++chars_read; + return current = ia.get_character(); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + char_int_type get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[out] result number of type @a NumberType + + @return whether conversion completed + + @note This function needs to respect the system's endianess, because + bytes in CBOR, MessagePack, and UBJSON are stored in network order + (big endian) and therefore need reordering on little endian systems. + */ + template + bool get_number(const input_format_t format, NumberType& result) + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) + { + return false; + } + + // reverse byte order prior to conversion if necessary + if (is_little_endian != InputIsLittleEndian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return true; + } + + /*! + @brief create a string by reading characters from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of characters to read + @param[out] result string created by reading @a len bytes + + @return whether string creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + */ + template + bool get_string(const input_format_t format, + const NumberType len, + string_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + }; + return success; + } + + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @param[in] format the current format (for diagnostics) + @param[in] context further context information (for diagnostics) + @return whether the last read character is not EOF + */ + JSON_HEDLEY_NON_NULL(3) + bool unexpect_eof(const input_format_t format, const char* context) const + { + if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) + { + return sax->parse_error(chars_read, "", + parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); + } + return true; + } + + /*! + @return a string representation of the last read byte + */ + std::string get_token_string() const + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); + return std::string{cr.data()}; + } + + /*! + @param[in] format the current format + @param[in] detail a detailed error message + @param[in] context further context information + @return a message string to use in the parse_error exceptions + */ + std::string exception_message(const input_format_t format, + const std::string& detail, + const std::string& context) const + { + std::string error_msg = "syntax error while parsing "; + + switch (format) + { + case input_format_t::cbor: + error_msg += "CBOR"; + break; + + case input_format_t::msgpack: + error_msg += "MessagePack"; + break; + + case input_format_t::ubjson: + error_msg += "UBJSON"; + break; + + case input_format_t::bson: + error_msg += "BSON"; + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + + return error_msg + " " + context + ": " + detail; + } + + private: + /// input adapter + InputAdapterType ia; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the SAX parser + json_sax_t* sax = nullptr; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +enum class parse_event_t : uint8_t +{ + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value +}; + +template +using parser_callback_t = + std::function; + +/*! +@brief syntax analysis + +This class implements a recursive descent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(InputAdapterType&& adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) + { + // read first token + get_token(); + } + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + if (callback) + { + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + sax_parse_internal(&sdp); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + else + { + json_sax_dom_parser sdp(result, allow_exceptions); + sax_parse_internal(&sdp); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + json_sax_acceptor sax_acceptor; + return sax_parse(&sax_acceptor, strict); + } + + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse(SAX* sax, const bool strict = true) + { + (void)detail::is_sax_static_asserts {}; + const bool result = sax_parse_internal(sax); + + // strict mode: next byte must be EOF + if (result && strict && (get_token() != token_type::end_of_input)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"))); + } + + return result; + } + + private: + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse_internal(SAX* sax) + { + // stack to remember the hierarchy of structured values we are parsing + // true = array; false = object + std::vector states; + // value to avoid a goto (see comment where set to true) + bool skip_to_state_evaluation = false; + + while (true) + { + if (!skip_to_state_evaluation) + { + // invariant: get_token() was called before each iteration + switch (last_token) + { + case token_type::begin_object: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + // closing } -> we are done + if (get_token() == token_type::end_object) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + break; + } + + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::value_string, "object key"))); + } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::name_separator, "object separator"))); + } + + // remember we are now inside an object + states.push_back(false); + + // parse values + get_token(); + continue; + } + + case token_type::begin_array: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + // closing ] -> we are done + if (get_token() == token_type::end_array) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + break; + } + + // remember we are now inside an array + states.push_back(true); + + // parse values (no need to call get_token) + continue; + } + + case token_type::value_float: + { + const auto res = m_lexer.get_number_float(); + + if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) + { + return false; + } + + break; + } + + case token_type::literal_false: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) + { + return false; + } + break; + } + + case token_type::literal_null: + { + if (JSON_HEDLEY_UNLIKELY(!sax->null())) + { + return false; + } + break; + } + + case token_type::literal_true: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) + { + return false; + } + break; + } + + case token_type::value_integer: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) + { + return false; + } + break; + } + + case token_type::value_string: + { + if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) + { + return false; + } + break; + } + + case token_type::value_unsigned: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) + { + return false; + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::uninitialized, "value"))); + } + + default: // the last token was unexpected + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::literal_or_value, "value"))); + } + } + } + else + { + skip_to_state_evaluation = false; + } + + // we reached this line after we successfully parsed a value + if (states.empty()) + { + // empty stack: we reached the end of the hierarchy: done + return true; + } + + if (states.back()) // array + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse a new value + get_token(); + continue; + } + + // closing ] + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + + // We are done with this array. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_array, "array"))); + } + else // object + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse key + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::value_string, "object key"))); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::name_separator, "object separator"))); + } + + // parse values + get_token(); + continue; + } + + // closing } + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + + // We are done with this object. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_object, "object"))); + } + } + } + + /// get next token from lexer + token_type get_token() + { + return last_token = m_lexer.scan(); + } + + std::string exception_message(const token_type expected, const std::string& context) + { + std::string error_msg = "syntax error "; + + if (!context.empty()) + { + error_msg += "while parsing " + context + " "; + } + + error_msg += "- "; + + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + return error_msg; + } + + private: + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + ++m_it; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + --m_it; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; +template class iteration_proxy_value; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + friend iteration_proxy_value; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief const copy constructor + @param[in] other const iterator to copy from + @note This copy constructor had to be defined explicitly to circumvent a bug + occurring on msvc v19.0 compiler (VS 2015) debug build. For more + information refer to: https://github.com/nlohmann/json/issues/1608 + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return !operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return !other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return !operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return !operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const typename object_t::key_type& key() const + { + JSON_ASSERT(m_object != nullptr); + + if (JSON_HEDLEY_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it {}; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // all_of +#include // isdigit +#include // max +#include // accumulate +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @brief append another JSON pointer at the end of this JSON pointer + + @param[in] ptr JSON pointer to append + @return JSON pointer with @a ptr appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::string) to append a reference token + @sa @ref operator/=(std::size_t) to append an array index + @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), + ptr.reference_tokens.begin(), + ptr.reference_tokens.end()); + return *this; + } + + /*! + @brief append an unescaped reference token at the end of this JSON pointer + + @param[in] token reference token to append + @return JSON pointer with @a token appended without escaping @a token + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + @sa @ref operator/=(std::size_t) to append an array index + @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::string token) + { + push_back(std::move(token)); + return *this; + } + + /*! + @brief append an array index at the end of this JSON pointer + + @param[in] array_idx array index to append + @return JSON pointer with @a array_idx appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + @sa @ref operator/=(std::string) to append a reference token + @sa @ref operator/(const json_pointer&, std::string) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::size_t array_idx) + { + return *this /= std::to_string(array_idx); + } + + /*! + @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + + @param[in] lhs JSON pointer + @param[in] rhs JSON pointer + @return a new JSON pointer with @a rhs appended to @a lhs + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a lhs and @a rhs. + + @sa @ref operator/=(const json_pointer&) to append a JSON pointer + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& lhs, + const json_pointer& rhs) + { + return json_pointer(lhs) /= rhs; + } + + /*! + @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] token reference token + @return a new JSON pointer with unescaped @a token appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::string) to append a reference token + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::string token) + { + return json_pointer(ptr) /= std::move(token); + } + + /*! + @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] array_idx array index + @return a new JSON pointer with @a array_idx appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa @ref operator/=(std::size_t) to append an array index + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) + { + return json_pointer(ptr) /= array_idx; + } + + /*! + @brief returns the parent of this JSON pointer + + @return parent of this JSON pointer; in case this JSON pointer is the root, + the root itself is returned + + @complexity Linear in the length of the JSON pointer. + + @liveexample{The example shows the result of `parent_pointer` for different + JSON Pointers.,json_pointer__parent_pointer} + + @since version 3.6.0 + */ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + + /*! + @brief remove last reference token + + @pre not `empty()` + + @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + void pop_back() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + reference_tokens.pop_back(); + } + + /*! + @brief return last reference token + + @pre not `empty()` + @return last reference token + + @liveexample{The example shows the usage of `back`.,json_pointer__back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + const std::string& back() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + return reference_tokens.back(); + } + + /*! + @brief append an unescaped token at the end of the reference pointer + + @param[in] token token to add + + @complexity Amortized constant. + + @liveexample{The example shows the result of `push_back` for different + JSON Pointers.,json_pointer__push_back} + + @since version 3.6.0 + */ + void push_back(const std::string& token) + { + reference_tokens.push_back(token); + } + + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) + { + reference_tokens.push_back(std::move(token)); + } + + /*! + @brief return whether pointer points to the root document + + @return true iff the JSON pointer points to the root document + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example shows the result of `empty` for different JSON + Pointers.,json_pointer__empty} + + @since version 3.6.0 + */ + bool empty() const noexcept + { + return reference_tokens.empty(); + } + + private: + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index begins not with a digit + @throw out_of_range.404 if string @a s could not be converted to an integer + @throw out_of_range.410 if an array index exceeds size_type + */ + static typename BasicJsonType::size_type array_index(const std::string& s) + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + s + + "' must not begin with '0'")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); + } + + std::size_t processed_chars = 0; + unsigned long long res = 0; + JSON_TRY + { + res = std::stoull(s, &processed_chars); + } + JSON_CATCH(std::out_of_range&) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + // check if the string was completely read + if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + // only triggered on special platforms (like 32bit), see also + // https://github.com/nlohmann/json/pull/2203 + if (res >= static_cast((std::numeric_limits::max)())) + { + JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE + } + + return static_cast(res); + } + + json_pointer top() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->type()) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + result = &result->operator[](array_index(reference_token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->is_null()) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums || reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index(reference_token)); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // use unchecked array access + ptr = &ptr->operator[](array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + */ + bool contains(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + if (!ptr->contains(reference_token)) + { + // we did not find the key in the object + return false; + } + + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) + { + // invalid char + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) + { + if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) + { + // first char should be between '1' and '9' + return false; + } + for (std::size_t i = 1; i < reference_token.size(); i++) + { + if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) + { + // other char should be between '0' and '9' + return false; + } + } + } + + const auto idx = array_index(reference_token); + if (idx >= ptr->size()) + { + // index out of range + return false; + } + + ptr = &ptr->operator[](idx); + break; + } + + default: + { + // we do not expect primitive values if there is still a + // reference token to process + return false; + } + } + } + + // no reference token left means we found a primitive value + return true; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == 0 (if slash == std::string::npos) + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = (slash == std::string::npos) ? 0 : slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + JSON_ASSERT(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || + (reference_token[pos + 1] != '0' && + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} + } + + /// escape "~" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.type()) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_HEDLEY_UNLIKELY(!value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + /*! + @brief compares two JSON pointers for equality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is equal to @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + /*! + @brief compares two JSON pointers for inequality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is not equal @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} // namespace nlohmann + +// #include + + +#include +#include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)) + , is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + template < + class... Args, + enable_if_t::value, int> = 0 > + json_ref(Args && ... args) + : owned_value(std::forward(args)...) + , value_ref(&owned_value) + , is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + json_ref& operator=(json_ref&&) = delete; + ~json_ref() = default; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue = true; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits +#include // string +#include // isnan, isinf + +// #include + +// #include + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) noexcept + : v(vec) + {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) noexcept + : stream(s) + {} + + void write_character(CharType c) override + { + stream.put(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template> +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(StringType& s) noexcept + : str(s) + {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template> +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + JSON_ASSERT(oa); + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + { + write_bson_object(*j.m_value.object); + break; + } + + default: + { + JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); + } + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xF5) + : to_char_type(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(to_char_type(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: + { + if (std::isnan(j.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); + } + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::binary: + { + if (j.m_value.binary->has_subtype()) + { + write_number(static_cast(0xd8)); + write_number(j.m_value.binary->subtype()); + } + + // step 1: write control byte and the binary array size + const auto N = j.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast(0x40 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(to_char_type(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xC3) + : to_char_type(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(to_char_type(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(to_char_type(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(to_char_type(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(to_char_type(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: + { + write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(to_char_type(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(to_char_type(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(to_char_type(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(to_char_type(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(to_char_type(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::binary: + { + // step 0: determine if the binary type has a set subtype to + // determine whether or not to use the ext or fixext types + const bool use_ext = j.m_value.binary->has_subtype(); + + // step 1: write control byte and the byte string length + const auto N = j.m_value.binary->size(); + if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type{}; + bool fixed = true; + if (use_ext) + { + switch (N) + { + case 1: + output_type = 0xD4; // fixext 1 + break; + case 2: + output_type = 0xD5; // fixext 2 + break; + case 4: + output_type = 0xD6; // fixext 4 + break; + case 8: + output_type = 0xD7; // fixext 8 + break; + case 16: + output_type = 0xD8; // fixext 16 + break; + default: + output_type = 0xC7; // ext 8 + fixed = false; + break; + } + + } + else + { + output_type = 0xC4; // bin 8 + fixed = false; + } + + oa->write_character(to_char_type(output_type)); + if (!fixed) + { + write_number(static_cast(N)); + } + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC8 // ext 16 + : 0xC5; // bin 16 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC9 // ext 32 + : 0xC6; // bin 32 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + + // step 1.5: if this is an ext type, write the subtype + if (use_ext) + { + write_number(static_cast(j.m_value.binary->subtype())); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(to_char_type(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(to_char_type(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(to_char_type('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + { + oa->write_character(j.m_value.boolean + ? to_char_type('T') + : to_char_type('F')); + } + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(to_char_type('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.array->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type && !j.m_value.binary->empty()) + { + JSON_ASSERT(use_count); + oa->write_character(to_char_type('$')); + oa->write_character('U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + } + + if (use_type) + { + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + j.m_value.binary->size()); + } + else + { + for (size_t i = 0; i < j.m_value.binary->size(); ++i) + { + oa->write_character(to_char_type('U')); + oa->write_character(j.m_value.binary->data()[i]); + } + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(to_char_type('{')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.object->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (!use_count) + { + oa->write_character(to_char_type('}')); + } + + break; + } + + default: + break; + } + } + + private: + ////////// + // BSON // + ////////// + + /*! + @return The size of a BSON document entry header, including the id marker + and the entry name size (and its null-terminator). + */ + static std::size_t calc_bson_entry_header_size(const string_t& name) + { + const auto it = name.find(static_cast(0)); + if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) + { + JSON_THROW(out_of_range::create(409, + "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")")); + } + + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + } + + /*! + @brief Writes the given @a element_type and @a name to the output adapter + */ + void write_bson_entry_header(const string_t& name, + const std::uint8_t element_type) + { + oa->write_character(to_char_type(element_type)); // boolean + oa->write_characters( + reinterpret_cast(name.c_str()), + name.size() + 1u); + } + + /*! + @brief Writes a BSON element with key @a name and boolean value @a value + */ + void write_bson_boolean(const string_t& name, + const bool value) + { + write_bson_entry_header(name, 0x08); + oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and double value @a value + */ + void write_bson_double(const string_t& name, + const double value) + { + write_bson_entry_header(name, 0x01); + write_number(value); + } + + /*! + @return The size of the BSON-encoded string in @a value + */ + static std::size_t calc_bson_string_size(const string_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and string value @a value + */ + void write_bson_string(const string_t& name, + const string_t& value) + { + write_bson_entry_header(name, 0x02); + + write_number(static_cast(value.size() + 1ul)); + oa->write_characters( + reinterpret_cast(value.c_str()), + value.size() + 1); + } + + /*! + @brief Writes a BSON element with key @a name and null value + */ + void write_bson_null(const string_t& name) + { + write_bson_entry_header(name, 0x0A); + } + + /*! + @return The size of the BSON-encoded integer @a value + */ + static std::size_t calc_bson_integer_size(const std::int64_t value) + { + return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and integer @a value + */ + void write_bson_integer(const string_t& name, + const std::int64_t value) + { + if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) + { + write_bson_entry_header(name, 0x10); // int32 + write_number(static_cast(value)); + } + else + { + write_bson_entry_header(name, 0x12); // int64 + write_number(static_cast(value)); + } + } + + /*! + @return The size of the BSON-encoded unsigned integer in @a j + */ + static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept + { + return (value <= static_cast((std::numeric_limits::max)())) + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and unsigned @a value + */ + void write_bson_unsigned(const string_t& name, + const std::uint64_t value) + { + if (value <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x10 /* int32 */); + write_number(static_cast(value)); + } + else if (value <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x12 /* int64 */); + write_number(static_cast(value)); + } + else + { + JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); + } + } + + /*! + @brief Writes a BSON element with key @a name and object @a value + */ + void write_bson_object_entry(const string_t& name, + const typename BasicJsonType::object_t& value) + { + write_bson_entry_header(name, 0x03); // object + write_bson_object(value); + } + + /*! + @return The size of the BSON-encoded array @a value + */ + static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) + { + std::size_t array_index = 0ul; + + const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) + { + return result + calc_bson_element_size(std::to_string(array_index++), el); + }); + + return sizeof(std::int32_t) + embedded_document_size + 1ul; + } + + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and array @a value + */ + void write_bson_array(const string_t& name, + const typename BasicJsonType::array_t& value) + { + write_bson_entry_header(name, 0x04); // array + write_number(static_cast(calc_bson_array_size(value))); + + std::size_t array_index = 0ul; + + for (const auto& el : value) + { + write_bson_element(std::to_string(array_index++), el); + } + + oa->write_character(to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number(static_cast(value.size())); + write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); + + oa->write_characters(reinterpret_cast(value.data()), value.size()); + } + + /*! + @brief Calculates the size necessary to serialize the JSON value @a j with its @a name + @return The calculated size for the BSON document entry for @a j with the given @a name. + */ + static std::size_t calc_bson_element_size(const string_t& name, + const BasicJsonType& j) + { + const auto header_size = calc_bson_entry_header_size(name); + switch (j.type()) + { + case value_t::object: + return header_size + calc_bson_object_size(*j.m_value.object); + + case value_t::array: + return header_size + calc_bson_array_size(*j.m_value.array); + + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_value.binary); + + case value_t::boolean: + return header_size + 1ul; + + case value_t::number_float: + return header_size + 8ul; + + case value_t::number_integer: + return header_size + calc_bson_integer_size(j.m_value.number_integer); + + case value_t::number_unsigned: + return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); + + case value_t::string: + return header_size + calc_bson_string_size(*j.m_value.string); + + case value_t::null: + return header_size + 0ul; + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); + return 0ul; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Serializes the JSON value @a j to BSON and associates it with the + key @a name. + @param name The name to associate with the JSON entity @a j within the + current BSON document + @return The size of the BSON entry + */ + void write_bson_element(const string_t& name, + const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + return write_bson_object_entry(name, *j.m_value.object); + + case value_t::array: + return write_bson_array(name, *j.m_value.array); + + case value_t::binary: + return write_bson_binary(name, *j.m_value.binary); + + case value_t::boolean: + return write_bson_boolean(name, j.m_value.boolean); + + case value_t::number_float: + return write_bson_double(name, j.m_value.number_float); + + case value_t::number_integer: + return write_bson_integer(name, j.m_value.number_integer); + + case value_t::number_unsigned: + return write_bson_unsigned(name, j.m_value.number_unsigned); + + case value_t::string: + return write_bson_string(name, *j.m_value.string); + + case value_t::null: + return write_bson_null(name); + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); + return; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Calculates the size of the BSON serialization of the given + JSON-object @a j. + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) + { + std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), + [](size_t result, const typename BasicJsonType::object_t::value_type & el) + { + return result += calc_bson_element_size(el.first, el.second); + }); + + return sizeof(std::int32_t) + document_size + 1ul; + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson_object(const typename BasicJsonType::object_t& value) + { + write_number(static_cast(calc_bson_object_size(value))); + + for (const auto& el : value) + { + write_bson_element(el.first, el.second); + } + + oa->write_character(to_char_type(0x00)); + } + + ////////// + // CBOR // + ////////// + + static constexpr CharType get_cbor_float_prefix(float /*unused*/) + { + return to_char_type(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double /*unused*/) + { + return to_char_type(0xFB); // Double-Precision Float + } + + ///////////// + // MsgPack // + ///////////// + + static constexpr CharType get_msgpack_float_prefix(float /*unused*/) + { + return to_char_type(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double /*unused*/) + { + return to_char_type(0xCB); // float 64 + } + + //////////// + // UBJSON // + //////////// + + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) + { + oa->write_character(get_ubjson_float_prefix(n)); + } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + } + + // UBJSON: write number (signed integer) + template < typename NumberType, typename std::enable_if < + std::is_signed::value&& + !std::is_floating_point::value, int >::type = 0 > + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + */ + CharType ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'i'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'U'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'I'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'l'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: // fallthrough + case value_t::binary: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + static constexpr CharType get_ubjson_float_prefix(float /*unused*/) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double /*unused*/) + { + return 'D'; // float 64 + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /* + @brief write a number to output input + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + @tparam OutputIsLittleEndian Set to true if output data is + required to be little endian + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian != OutputIsLittleEndian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + void write_compact_float(const number_float_t n, detail::input_format_t format) + { + if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && + static_cast(n) <= static_cast((std::numeric_limits::max)()) && + static_cast(static_cast(n)) == static_cast(n)) + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(static_cast(n)) + : get_msgpack_float_prefix(static_cast(n))); + write_number(static_cast(n)); + } + else + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(n) + : get_msgpack_float_prefix(n)); + write_number(n); + } + } + + public: + // The following to_char_type functions are implement the conversion + // between uint8_t and CharType. In case CharType is not unsigned, + // such a conversion is required to allow values greater than 128. + // See for a discussion. + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return *reinterpret_cast(&x); + } + + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > + static CharType to_char_type(std::uint8_t x) noexcept + { + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + static_assert(std::is_trivial::value, "CharType must be trivial"); + CharType result; + std::memcpy(&result, &x, sizeof(x)); + return result; + } + + template::value>* = nullptr> + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return x; + } + + template < typename InputCharType, typename C = CharType, + enable_if_t < + std::is_signed::value && + std::is_signed::value && + std::is_same::type>::value + > * = nullptr > + static constexpr CharType to_char_type(InputCharType x) noexcept + { + return x; + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string, char_traits +#include // is_same +#include // move + +// #include + + +#include // array +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove +#include // numeric_limits +#include // conditional + +// #include + + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + JSON_ASSERT(x.e == y.e); + JSON_ASSERT(x.f >= y.f); + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + JSON_ASSERT(x.f != 0); + + while ((x.f >> 63u) == 0) + { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + JSON_ASSERT(delta >= 0); + JSON_ASSERT(((x.f << delta) >> delta) == x.f); + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional::type; + + const std::uint64_t bits = reinterpret_bits(value); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = E == 0; + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && E > 1; + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array kCachedPowers = + { + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + } + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + JSON_ASSERT(e >= -1500); + JSON_ASSERT(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + JSON_ASSERT(index >= 0); + JSON_ASSERT(static_cast(index) < kCachedPowers.size()); + + const cached_power cached = kCachedPowers[static_cast(index)]; + JSON_ASSERT(kAlpha <= cached.e + e + 64); + JSON_ASSERT(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, + std::uint64_t rest, std::uint64_t ten_k) +{ + JSON_ASSERT(len >= 1); + JSON_ASSERT(dist <= delta); + JSON_ASSERT(rest <= delta); + JSON_ASSERT(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + && delta - rest >= ten_k + && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) + { + JSON_ASSERT(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + JSON_ASSERT(M_plus.e >= kAlpha); + JSON_ASSERT(M_plus.e <= kGamma); + + std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + JSON_ASSERT(p1 > 0); + + std::uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + JSON_ASSERT(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +JSON_HEDLEY_NON_NULL(1) +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + JSON_ASSERT(m_plus.e == m_minus.e); + JSON_ASSERT(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +JSON_HEDLEY_NON_NULL(1) +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* append_exponent(char* buf, int e) +{ + JSON_ASSERT(e > -1000); + JSON_ASSERT(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + auto k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + JSON_ASSERT(min_exp < 0); + JSON_ASSERT(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n) - static_cast(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast(n) + 2); + } + + if (0 < n && n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + JSON_ASSERT(k > n); + + std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(k)); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +JSON_HEDLEY_RETURNS_NON_NULL +char* to_chars(char* first, const char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + JSON_ASSERT(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + JSON_ASSERT(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + JSON_ASSERT(last - first >= kMaxExp + 2); + JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +/// how to treat decoding errors +enum class error_handler_t +{ + strict, ///< throw a type_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore ///< ignore invalid UTF-8 sequences +}; + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using binary_char_t = typename BasicJsonType::binary_t::value_type; + static constexpr std::uint8_t UTF8_ACCEPT = 0; + static constexpr std::uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + @param[in] error_handler_ how to react on decoding errors + */ + serializer(output_adapter_t s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict) + : o(std::move(s)) + , loc(std::localeconv()) + , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) + , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) + , indent_char(ichar) + , indent_string(512, indent_char) + , error_handler(error_handler_) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + serializer(serializer&&) = delete; + serializer& operator=(serializer&&) = delete; + ~serializer() = default; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + - binary values are serialized as objects containing the subtype and the + byte array + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, + const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(!val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(!val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::binary: + { + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"bytes\": [", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_characters(", ", 2); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\n", 3); + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"subtype\": ", 11); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + } + else + { + o->write_characters("null", 4); + } + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_characters("{\"bytes\":[", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_character(','); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\"subtype\":", 12); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + o->write_character('}'); + } + else + { + o->write_characters("null}", 5); + } + } + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + std::uint32_t codepoint; + std::uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + // number of bytes written at the point of the last valid byte + std::size_t bytes_after_last_accept = 0; + std::size_t undumped_chars = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0u + (codepoint >> 10u)), + static_cast(0xDC00u + (codepoint & 0x3FFu))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + // remember the byte position of this accept + bytes_after_last_accept = bytes; + undumped_chars = 0; + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); + } + + case error_handler_t::ignore: + case error_handler_t::replace: + { + // in case we saw this character the first time, we + // would like to read it again, because the byte + // may be OK for itself, but just not OK for the + // previous sequence + if (undumped_chars > 0) + { + --i; + } + + // reset length buffer to the last accepted index; + // thus removing/ignoring the invalid characters + bytes = bytes_after_last_accept; + + if (error_handler == error_handler_t::replace) + { + // add a replacement character + if (ensure_ascii) + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'u'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'd'; + } + else + { + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + } + + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + break; + } + + default: // decode found yet incomplete multi-byte code point + { + if (!ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + ++undumped_chars; + break; + } + } + } + + // we finished processing the string + if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); + } + + case error_handler_t::ignore: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + break; + } + + case error_handler_t::replace: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + // add a replacement character + if (ensure_ascii) + { + o->write_characters("\\ufffd", 6); + } + else + { + o->write_characters("\xEF\xBF\xBD", 3); + } + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + } + } + + /*! + @brief count digits + + Count the number of decimal (base 10) digits for an input unsigned integer. + + @param[in] x unsigned integer number to count its digits + @return number of decimal digits + */ + inline unsigned int count_digits(number_unsigned_t x) noexcept + { + unsigned int n_digits = 1; + for (;;) + { + if (x < 10) + { + return n_digits; + } + if (x < 100) + { + return n_digits + 1; + } + if (x < 1000) + { + return n_digits + 2; + } + if (x < 10000) + { + return n_digits + 3; + } + x = x / 10000u; + n_digits += 4; + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < typename NumberType, detail::enable_if_t < + std::is_same::value || + std::is_same::value || + std::is_same::value, + int > = 0 > + void dump_integer(NumberType x) + { + static constexpr std::array, 100> digits_to_99 + { + { + {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, + {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, + {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, + {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, + {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, + {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, + {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, + {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, + {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, + {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, + } + }; + + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + // use a pointer to fill the buffer + auto buffer_ptr = number_buffer.begin(); + + const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 + number_unsigned_t abs_value; + + unsigned int n_chars; + + if (is_negative) + { + *buffer_ptr = '-'; + abs_value = remove_sign(static_cast(x)); + + // account one more byte for the minus sign + n_chars = 1 + count_digits(abs_value); + } + else + { + abs_value = static_cast(x); + n_chars = count_digits(abs_value); + } + + // spare 1 byte for '\0' + JSON_ASSERT(n_chars < number_buffer.size() - 1); + + // jump to the end to generate the string from backward + // so we later avoid reversing the result + buffer_ptr += n_chars; + + // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu + // See: https://www.youtube.com/watch?v=o4-CwDo2zpg + while (abs_value >= 100) + { + const auto digits_index = static_cast((abs_value % 100)); + abs_value /= 100; + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + + if (abs_value >= 10) + { + const auto digits_index = static_cast(abs_value); + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + else + { + *(--buffer_ptr) = static_cast('0' + abs_value); + } + + o->write_characters(number_buffer.data(), n_chars); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (!std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || + (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + JSON_ASSERT(len > 0); + // check if buffer was large enough + JSON_ASSERT(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + JSON_ASSERT((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' && decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return c == '.' || c == 'e'; + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const std::uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6u) + : (0xFFu >> type) & (byte); + + std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + JSON_ASSERT(index < 400); + state = utf8d[index]; + return state; + } + + /* + * Overload to make the compiler happy while it is instantiating + * dump_integer for number_unsigned_t. + * Must never be called. + */ + number_unsigned_t remove_sign(number_unsigned_t x) + { + JSON_ASSERT(false); // LCOV_EXCL_LINE + return x; // LCOV_EXCL_LINE + } + + /* + * Helper function for dump_integer + * + * This function takes a negative signed integer and returns its absolute + * value as unsigned integer. The plus/minus shuffling is necessary as we can + * not directly remove the sign of an arbitrary signed integer as the + * absolute values of INT_MIN and INT_MAX are usually not the same. See + * #1708 for details. + */ + inline number_unsigned_t remove_sign(number_integer_t x) noexcept + { + JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); + return static_cast(-(x + 1)) + 1; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; + + /// error_handler how to react on decoding errors + const error_handler_t error_handler; +}; +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + + +#include // less +#include // allocator +#include // pair +#include // vector + +namespace nlohmann +{ + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using typename Container::iterator; + using typename Container::const_iterator; + using typename Container::size_type; + using typename Container::value_type; + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(const key_type& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return {it, false}; + } + } + Container::emplace_back(key, t); + return {--this->end(), true}; + } + + T& operator[](const Key& key) + { + return emplace(key, T{}).first->second; + } + + const T& operator[](const Key& key) const + { + return at(key); + } + + T& at(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + const T& at(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + throw std::out_of_range("key not found"); + } + + size_type erase(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + iterator erase(iterator pos) + { + auto it = pos; + + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return pos; + } + + size_type count(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return 1; + } + } + return 0; + } + + iterator find(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == value.first) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } +}; + +} // namespace nlohmann + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam BinaryType type for packed binary data for compatibility with binary +serialization formats (`std::vector` by default; will be used in +@ref binary_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): + JSON values have + [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](https://en.cppreference.com/w/cpp/named_req/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from https://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + + template + friend class ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + template + friend class ::nlohmann::detail::json_sax_dom_parser; + template + friend class ::nlohmann::detail::json_sax_dom_callback_parser; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer_base; + + template + static ::nlohmann::detail::parser parser( + InputAdapterType adapter, + detail::parser_callback_tcb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false + ) + { + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); + } + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + template + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// JSON Pointer, see @ref nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// how to treat decoding errors + using error_handler_t = detail::error_handler_t; + /// how to treat CBOR tags + using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + using input_format_t = detail::input_format_t; + /// SAX interface type, see @ref nlohmann::json_sax + using json_sax_t = json_sax; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2020 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /*! + @brief a type for a packed binary type + + This type is a type designed to carry binary data that appears in various + serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and + BSON's generic binary subtype. This type is NOT a part of standard JSON and + exists solely for compatibility with these binary types. As such, it is + simply defined as an ordered sequence of zero or more byte values. + + Additionally, as an implementation detail, the subtype of the binary data is + carried around as a `std::uint8_t`, which is compatible with both of the + binary data formats that use binary subtyping, (though the specific + numbering is incompatible with each other, and it is up to the user to + translate between them). + + [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type + as: + > Major type 2: a byte string. The string's length in bytes is represented + > following the rules for positive integers (major type 0). + + [MessagePack's documentation on the bin type + family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) + describes this type as: + > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes + > in addition to the size of the byte array. + + [BSON's specifications](http://bsonspec.org/spec.html) describe several + binary types; however, this type is intended to represent the generic binary + type which has the description: + > Generic binary subtype - This is the most commonly used binary subtype and + > should be the 'default' for drivers and tools. + + None of these impose any limitations on the internal representation other + than the basic unit of storage be some type of array whose parts are + decomposable into bytes. + + The default representation of this binary format is a + `std::vector`, which is a very common way to represent a byte + array in modern C++. + + #### Default type + + The default values for @a BinaryType is `std::vector` + + #### Storage + + Binary Arrays are stored as pointers in a @ref basic_json type. That is, + for any access to array values, a pointer of the type `binary_t*` must be + dereferenced. + + #### Notes on subtypes + + - CBOR + - Binary values are represented as byte strings. No subtypes are + supported and will be ignored when CBOR is written. + - MessagePack + - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, + or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) + is used. For other sizes, the ext family (ext8, ext16, ext32) is used. + The subtype is then added as singed 8-bit integer. + - If no subtype is given, the bin family (bin8, bin16, bin32) is used. + - BSON + - If a subtype is given, it is used and added as unsigned 8-bit integer. + - If no subtype is given, the generic binary subtype 0x00 is used. + + @sa @ref binary -- create a binary array + + @since version 3.8.0 + */ + using binary_t = nlohmann::byte_container_with_subtype; + /// @} + + private: + + /// helper for exception-safe object creation + template + JSON_HEDLEY_RETURNS_NON_NULL + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + JSON_ASSERT(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::binary: + { + binary = create(); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + /// constructor for binary arrays + json_value(const typename binary_t::container_type& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays + json_value(typename binary_t::container_type&& value) + { + binary = create(std::move(value)); + } + + /// constructor for binary arrays (internal type) + json_value(const binary_t& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays (internal type) + json_value(binary_t&& value) + { + binary = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; + + // move the top-level items to stack + if (t == value_t::array) + { + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); + } + else if (t == value_t::object) + { + stack.reserve(object->size()); + for (auto&& it : *object) + { + stack.push_back(std::move(it.second)); + } + } + + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), + std::back_inserter(stack)); + + current_item.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } + + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + case value_t::binary: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); + JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); + JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); + JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = detail::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = detail::parser_callback_t; + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + binary | empty array + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + - **binary**: @ref binary_t / `std::vector` may be used, + unfortunately because string literals cannot be distinguished from binary + character arrays by the C++ type system, all types compatible with `const + char*` will be directed to the string constructor instead. This is both + for backwards compatibility, and due to the fact that a binary type is not + a standard JSON type. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template < typename CompatibleType, + typename U = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different + template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + using other_binary_t = typename BasicJsonType::binary_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::binary: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (!type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create a binary array (without subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = init; + return res; + } + + /*! + @brief explicitly create a binary array (with subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + @param[in] subtype subtype to use in MessagePack and BSON + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(init, subtype); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = std::move(init); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(std::move(init), subtype); + return res; + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See https://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template < class InputIT, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type = 0 > + basic_json(InputIT first, InputIT last) + { + JSON_ASSERT(first.m_object != nullptr); + JSON_ASSERT(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::binary: + { + m_value = *first.m_object->m_value.binary; + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + template, + std::is_same>::value, int> = 0 > + basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + case value_t::binary: + { + m_value = *other.m_value.binary; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + basic_json& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] error_handler how to react on decoding errors; there are three + possible values: `strict` (throws and exception in case a decoding error + occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), + and `ignore` (ignore invalid UTF-8 sequences during serialization; all + bytes are copied to the output unchanged). + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded and @a error_handler is set to strict + + @note Binary values are serialized as object containing two keys: + - "bytes": an array of bytes as integers + - "subtype": the subtype as integer or "null" if the binary has no subtype + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0; error + handlers added in version 3.4.0; serialization of binary values added + in version 3.8.0. + */ + string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char, error_handler); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + binary | value_t::binary + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + @sa @ref is_binary() -- returns whether JSON value is a binary array + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() || is_string() || is_boolean() || is_number() || is_binary(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() || is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() || is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer || m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is a binary array + + This function returns true if and only if the JSON value is a binary array. + + @return `true` if type is binary array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_binary()` for all JSON + types.,is_binary} + + @since version 3.8.0 + */ + constexpr bool is_binary() const noexcept + { + return m_type == value_t::binary; + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_HEDLEY_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_HEDLEY_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.2.0 + */ + template < typename BasicJsonType, detail::enable_if_t < + !std::is_same::value&& + detail::is_basic_json::value, int > = 0 > + BasicJsonType get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && + detail::has_from_json::value && + !detail::has_non_default_from_json::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + detail::enable_if_t < !std::is_same::value && + detail::has_non_default_from_json::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + The value is filled into the input parameter by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType v; + JSONSerializer::from_json(*this, v); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + + @tparam ValueType the input parameter type. + + @return the input parameter, allowing chaining calls. + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get_to} + + @since version 3.3.0 + */ + template < typename ValueType, + detail::enable_if_t < + !detail::is_basic_json::value&& + detail::has_from_json::value, + int > = 0 > + ValueType & get_to(ValueType& v) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + // specialization to allow to call get_to with a basic_json value + // see https://github.com/nlohmann/json/issues/2175 + template::value, + int> = 0> + ValueType & get_to(ValueType& v) const + { + v = *this; + return v; + } + + template < + typename T, std::size_t N, + typename Array = T (&)[N], + detail::enable_if_t < + detail::has_from_json::value, int > = 0 > + Array get_to(T (&v)[N]) const + noexcept(noexcept(JSONSerializer::from_json( + std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template < typename PointerType, typename std::enable_if < + std::is_pointer::value&& + std::is_const::type>::value, int >::type = 0 > + constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get() noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr auto get() const noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template < typename ReferenceType, typename std::enable_if < + std::is_reference::value&& + std::is_const::type>::value, int >::type = 0 > + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + !std::is_pointer::value&& + !std::is_same>::value&& + !std::is_same::value&& + !detail::is_basic_json::value + && !std::is_same>::value +#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) + && !std::is_same::value +#endif + && detail::is_detected::value + , int >::type = 0 > + JSON_EXPLICIT operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /*! + @return reference to the binary value + + @throw type_error.302 if the value is not binary + + @sa @ref is_binary() to check if the value is binary + + @since version 3.8.0 + */ + binary_t& get_binary() + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); + } + + return *get_ptr(); + } + + /// @copydoc get_binary() + const binary_t& get_binary() const + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); + } + + return *get_ptr(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a key + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + // using std::is_convertible in a std::enable_if will fail when using explicit conversions + template < class ValueType, typename std::enable_if < + detail::is_getable::value + && !std::is_same::value, int >::type = 0 > + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return it->template get(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a ptr + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this).template get(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + JSON_HEDLEY_NON_NULL(3) + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + if (JSON_HEDLEY_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @sa @ref contains(KeyT&&) const -- checks whether a key exists + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /*! + @brief check the existence of an element in a JSON object + + Check whether an element exists in a JSON object with key equivalent to + @a key. If the element is not found or the JSON value is not an object, + false is returned. + + @note This method always returns false when executed on a JSON type + that is not an object. + + @param[in] key key value to check its existence. + + @return true if an element with specified @a key exists. If no such + element with such key is found or the JSON value is not an object, + false is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains} + + @sa @ref find(KeyT&&) -- returns an iterator to an object element + @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer + + @since version 3.6.0 + */ + template < typename KeyT, typename std::enable_if < + !std::is_same::type, json_pointer>::value, int >::type = 0 > + bool contains(KeyT && key) const + { + return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); + } + + /*! + @brief check the existence of an element in a JSON object given a JSON pointer + + Check whether the given JSON pointer @a ptr can be resolved in the current + JSON value. + + @note This method can be executed on any JSON value type. + + @param[in] ptr JSON pointer to check its existence. + + @return true if the JSON pointer can be resolved to a stored value, false + otherwise. + + @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} + + @sa @ref contains(KeyT &&) const -- checks the existence of a key + + @since version 3.7.0 + */ + bool contains(const json_pointer& ptr) const + { + return ptr.contains(this); + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto& el : j_object.items()) + { + std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; + } + @endcode + + The `items()` function also allows to use + [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) + (C++17): + + @code{cpp} + for (auto& [key, val] : j_object.items()) + { + std::cout << "key: " << key << ", value:" << val << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @warning Using `items()` on temporary objects is dangerous. Make sure the + object's lifetime exeeds the iteration. See + for more + information. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.1.0, structured bindings support since 3.5.0. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + binary | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + binary | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + binary | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + binary | An empty byte vector + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::binary: + { + m_value.binary->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // if val is moved from, basic_json move constructor marks it null so we do not call the destructor + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() && init.size() == 2 && (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return reference to the inserted element + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8, returns reference since 3.7.0 + */ + template + reference emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) +#ifdef JSON_HAS_CPP_17 + return m_value.array->emplace_back(std::forward(args)...); +#else + m_value.array->emplace_back(std::forward(args)...); + return m_value.array->back(); +#endif + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /// Helper for insertion of an iterator + /// @note: This uses std::distance to support GCC 4.8, + /// see https://github.com/nlohmann/json/pull/1257 + template + iterator insert_iterator(const_iterator pos, Args&& ... args) + { + iterator result(this); + JSON_ASSERT(m_value.array != nullptr); + + auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); + m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); + result.m_it.array_iterator = m_value.array->begin() + insert_pos; + + // This could have been written as: + // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + + return result; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, val); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, cnt, val); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + return insert_iterator(pos, ilist.begin(), ilist.end()); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() + || !last.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value from @a left with those of @a right. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. implemented as a friend function callable via ADL. + + @param[in,out] left JSON value to exchange the contents with + @param[in,out] right JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + friend void swap(reference left, reference right) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + left.swap(right); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other binary to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__binary_t} + + @since version 3.8.0 + */ + void swap(binary_t& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @copydoc swap(binary_t) + void swap(typename binary_t::container_type& other) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note that two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + Or you can self-defined operator equal function like this: + @code {.cpp} + bool my_equal(const_reference lhs, const_reference rhs) { + const auto lhs_type lhs.type(); + const auto rhs_type rhs.type(); + if (lhs_type == rhs_type) { + switch(lhs_type) + // self_defined case + case value_t::number_float: + return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); + // other cases remain the same with the original + ... + } + ... + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return *lhs.m_value.array == *rhs.m_value.array; + + case value_t::object: + return *lhs.m_value.object == *rhs.m_value.object; + + case value_t::null: + return true; + + case value_t::string: + return *lhs.m_value.string == *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean == rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer == rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float == rhs.m_value.number_float; + + case value_t::binary: + return *lhs.m_value.binary == *rhs.m_value.binary; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs == basic_json(rhs); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) == rhs; + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs != basic_json(rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) != rhs; + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + // note parentheses are necessary, see + // https://github.com/nlohmann/json/issues/1530 + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object) < (*rhs.m_value.object); + + case value_t::null: + return false; + + case value_t::string: + return (*lhs.m_value.string) < (*rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean) < (rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float) < (rhs.m_value.number_float); + + case value_t::binary: + return (*lhs.m_value.binary) < (*rhs.m_value.binary); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs < basic_json(rhs); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) < rhs; + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return !(rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs <= basic_json(rhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs > basic_json(rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) > rhs; + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return lhs >= basic_json(rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = o.width() > 0; + const auto indentation = pretty_print ? o.width() : 0; + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb or reading from the input @a i has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to + ignore comments. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(InputType&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief deserialize from a pair of character iterators + + The value_type of the iterator must be a integral type with size of 1, 2 or + 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. + + @param[in] first iterator to start of character range + @param[in] last iterator to end of character range + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) + static basic_json parse(detail::span_input_adapter&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief check if the input is valid JSON + + Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) + function, this function neither throws an exception in case of invalid JSON + input (i.e., a parse error) nor creates diagnostic information. + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return Whether the input read from @a i is valid JSON. + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `accept()` function reading + from a string.,accept__string} + */ + template + static bool accept(InputType&& i, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); + } + + template + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) + { + return parser(i.get(), nullptr, false, ignore_comments).accept(true); + } + + /*! + @brief generate SAX events + + The SAX event lister must follow the interface of @ref json_sax. + + This function reads from a compatible input. Examples are: + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in,out] sax SAX event listener + @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) + @param[in] strict whether the input has to be consumed completely + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default); only applies to the JSON file format. + + @return return value of the last processed SAX event + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the SAX consumer @a sax has + a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `sax_parse()` function + reading from string and processing the events with a user-defined SAX + event consumer.,sax_parse} + + @since version 3.2.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::forward(i)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_NON_NULL(3) + static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = i.get(); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + binary | `"binary"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + JSON_HEDLEY_RETURNS_NON_NULL + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value representable by a float* | Single-Precision Float | 0xFA + number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + binary | *size*: 0..23 | byte string | 0x40..0x57 + binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 + binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 + binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A + binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - byte strings terminated by "break" (0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half-precision floats (0xF9) + - break (0xFF) + + @param[in] j JSON value to serialize + @return CBOR serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; compact representation of floating-point numbers + since version 3.8.0 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value representable by a float* | float 32 | 0xCA + number_float | *any value NOT representable by a float* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + binary | *size*: 0..255 | bin 8 | 0xC4 + binary | *size*: 256..65535 | bin 16 | 0xC5 + binary | *size*: 65536..4294967295 | bin 32 | 0xC6 + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - byte strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack for the analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @note If the JSON data contains the binary type, the value stored is a list + of integers, as suggested by the UBJSON documentation. In particular, + this means that serialization and the deserialization of a JSON + containing binary values into UBJSON and back will result in a + different JSON object. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + + /*! + @brief Serializes the given JSON object `j` to BSON and returns a vector + containing the corresponding BSON-representation. + + BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are + stored as a single entity (a so-called document). + + The library uses the following mapping from JSON values types to BSON types: + + JSON value type | value/range | BSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | 0x0A + boolean | `true`, `false` | boolean | 0x08 + number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 + number_integer | -2147483648..2147483647 | int32 | 0x10 + number_integer | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 0..2147483647 | int32 | 0x10 + number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 9223372036854775808..18446744073709551615| -- | -- + number_float | *any value* | double | 0x01 + string | *any value* | string | 0x02 + array | *any value* | document | 0x04 + object | *any value* | document | 0x03 + binary | *any value* | binary | 0x05 + + @warning The mapping is **incomplete**, since only JSON-objects (and things + contained therein) can be serialized to BSON. + Also, integers larger than 9223372036854775807 cannot be serialized to BSON, + and the keys may not contain U+0000, since they are serialized a + zero-terminated c-strings. + + @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` + @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) + @throw type_error.317 if `!j.is_object()` + + @pre The input `j` is required to be an object: `j.is_object() == true`. + + @note Any BSON output created via @ref to_bson can be successfully parsed + by @ref from_bson. + + @param[in] j JSON value to serialize + @return BSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in BSON format.,to_bson} + + @sa http://bsonspec.org/spec.html + @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the + analogous deserialization + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + @sa @ref to_cbor(const basic_json&) for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + */ + static std::vector to_bson(const basic_json& j) + { + std::vector result; + to_bson(j, result); + return result; + } + + /*! + @brief Serializes the given JSON object `j` to BSON and forwards the + corresponding BSON-representation to the given output_adapter `o`. + @param j The JSON object to convert to BSON. + @param o The output adapter that receives the binary BSON representation. + @pre The input `j` shall be an object: `j.is_object() == true` + @sa @ref to_bson(const basic_json&) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + /*! + @copydoc to_bson(const basic_json&, detail::output_adapter) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Byte string | binary | 0x40..0x57 + Byte string | binary | 0x58 + Byte string | binary | 0x59 + Byte string | binary | 0x5A + Byte string | binary | 0x5B + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Null | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] tag_handler how to treat CBOR tags (optional, error by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0; added @a tag_handler parameter since 3.9.0. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); + } + + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + bin 8 | binary | 0xC4 + bin 16 | binary | 0xC5 + bin 32 | binary | 0xC6 + ext 8 | binary | 0xC7 + ext 16 | binary | 0xC8 + ext 32 | binary | 0xC9 + fixext 1 | binary | 0xD4 + fixext 2 | binary | 0xD5 + fixext 4 | binary | 0xD6 + fixext 8 | binary | 0xD7 + fixext 16 | binary | 0xD8 + negative fixint | number_integer | 0xE0-0xFF + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for + the related UBJSON format + @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for + the related BSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_msgpack(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for + the related MessagePack format + @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for + the related BSON format + + @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_ubjson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief Create a JSON value from an input in BSON format + + Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) + serialization format. + + The library maps BSON record types to JSON value types as follows: + + BSON type | BSON marker byte | JSON value type + --------------- | ---------------- | --------------------------- + double | 0x01 | number_float + string | 0x02 | string + document | 0x03 | object + array | 0x04 | array + binary | 0x05 | still unsupported + undefined | 0x06 | still unsupported + ObjectId | 0x07 | still unsupported + boolean | 0x08 | boolean + UTC Date-Time | 0x09 | still unsupported + null | 0x0A | null + Regular Expr. | 0x0B | still unsupported + DB Pointer | 0x0C | still unsupported + JavaScript Code | 0x0D | still unsupported + Symbol | 0x0E | still unsupported + JavaScript Code | 0x0F | still unsupported + int32 | 0x10 | number_integer + Timestamp | 0x11 | still unsupported + 128-bit decimal float | 0x13 | still unsupported + Max Key | 0x7F | still unsupported + Min Key | 0xFF | still unsupported + + @warning The mapping is **incomplete**. The unsupported mappings + are indicated in the table above. + + @param[in] i an input in BSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.114 if an unsupported BSON record type is encountered + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + BSON format to a JSON value.,from_bson} + + @sa http://bsonspec.org/spec.html + @sa @ref to_bson(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for + the related MessagePack format + @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the + related UBJSON format + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_bson(detail::input_adapter&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.empty()) + { + result = val; + return; + } + + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + break; + } + + // if there exists a parent it cannot be primitive + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // LCOV_EXCL_LINE + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_HEDLEY_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(json_pointer::array_index(last_path)); + } + }; + + // type check: top level value must be an array + if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_HEDLEY_UNLIKELY(!val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const auto from_path = get_value("move", "from", true).template get(); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const auto from_path = get_value("copy", "from", true).template get(); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_HEDLEY_UNLIKELY(!success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + default: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + return result; + } + + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/-"}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] apply_patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& apply_patch) + { + if (apply_patch.is_object()) + { + if (!is_object()) + { + *this = object(); + } + for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = apply_patch; + } + } + + /// @} +}; + +/*! +@brief user-defined to_string function for JSON values + +This function implements a user-defined to_string for JSON objects. + +@param[in] j a JSON object +@return a std::string object +*/ + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) +{ + return j.dump(); +} +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + return nlohmann::detail::hash(j); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less<::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +// C++20 prohibit function specialization in the std namespace. +#ifndef JSON_HAS_CPP_20 + +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value&& + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +#endif + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_ASSERT +#undef JSON_INTERNAL_CATCH +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef JSON_EXPLICIT + +// #include +#undef JSON_HEDLEY_ALWAYS_INLINE +#undef JSON_HEDLEY_ARM_VERSION +#undef JSON_HEDLEY_ARM_VERSION_CHECK +#undef JSON_HEDLEY_ARRAY_PARAM +#undef JSON_HEDLEY_ASSUME +#undef JSON_HEDLEY_BEGIN_C_DECLS +#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#undef JSON_HEDLEY_CLANG_HAS_FEATURE +#undef JSON_HEDLEY_CLANG_HAS_WARNING +#undef JSON_HEDLEY_COMPCERT_VERSION +#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#undef JSON_HEDLEY_CONCAT +#undef JSON_HEDLEY_CONCAT3 +#undef JSON_HEDLEY_CONCAT3_EX +#undef JSON_HEDLEY_CONCAT_EX +#undef JSON_HEDLEY_CONST +#undef JSON_HEDLEY_CONSTEXPR +#undef JSON_HEDLEY_CONST_CAST +#undef JSON_HEDLEY_CPP_CAST +#undef JSON_HEDLEY_CRAY_VERSION +#undef JSON_HEDLEY_CRAY_VERSION_CHECK +#undef JSON_HEDLEY_C_DECL +#undef JSON_HEDLEY_DEPRECATED +#undef JSON_HEDLEY_DEPRECATED_FOR +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#undef JSON_HEDLEY_DIAGNOSTIC_POP +#undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#undef JSON_HEDLEY_DMC_VERSION +#undef JSON_HEDLEY_DMC_VERSION_CHECK +#undef JSON_HEDLEY_EMPTY_BASES +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#undef JSON_HEDLEY_END_C_DECLS +#undef JSON_HEDLEY_FLAGS +#undef JSON_HEDLEY_FLAGS_CAST +#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_BUILTIN +#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_EXTENSION +#undef JSON_HEDLEY_GCC_HAS_FEATURE +#undef JSON_HEDLEY_GCC_HAS_WARNING +#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#undef JSON_HEDLEY_GCC_VERSION +#undef JSON_HEDLEY_GCC_VERSION_CHECK +#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#undef JSON_HEDLEY_GNUC_HAS_FEATURE +#undef JSON_HEDLEY_GNUC_HAS_WARNING +#undef JSON_HEDLEY_GNUC_VERSION +#undef JSON_HEDLEY_GNUC_VERSION_CHECK +#undef JSON_HEDLEY_HAS_ATTRIBUTE +#undef JSON_HEDLEY_HAS_BUILTIN +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_HAS_EXTENSION +#undef JSON_HEDLEY_HAS_FEATURE +#undef JSON_HEDLEY_HAS_WARNING +#undef JSON_HEDLEY_IAR_VERSION +#undef JSON_HEDLEY_IAR_VERSION_CHECK +#undef JSON_HEDLEY_IBM_VERSION +#undef JSON_HEDLEY_IBM_VERSION_CHECK +#undef JSON_HEDLEY_IMPORT +#undef JSON_HEDLEY_INLINE +#undef JSON_HEDLEY_INTEL_VERSION +#undef JSON_HEDLEY_INTEL_VERSION_CHECK +#undef JSON_HEDLEY_IS_CONSTANT +#undef JSON_HEDLEY_IS_CONSTEXPR_ +#undef JSON_HEDLEY_LIKELY +#undef JSON_HEDLEY_MALLOC +#undef JSON_HEDLEY_MESSAGE +#undef JSON_HEDLEY_MSVC_VERSION +#undef JSON_HEDLEY_MSVC_VERSION_CHECK +#undef JSON_HEDLEY_NEVER_INLINE +#undef JSON_HEDLEY_NON_NULL +#undef JSON_HEDLEY_NO_ESCAPE +#undef JSON_HEDLEY_NO_RETURN +#undef JSON_HEDLEY_NO_THROW +#undef JSON_HEDLEY_NULL +#undef JSON_HEDLEY_PELLES_VERSION +#undef JSON_HEDLEY_PELLES_VERSION_CHECK +#undef JSON_HEDLEY_PGI_VERSION +#undef JSON_HEDLEY_PGI_VERSION_CHECK +#undef JSON_HEDLEY_PREDICT +#undef JSON_HEDLEY_PRINTF_FORMAT +#undef JSON_HEDLEY_PRIVATE +#undef JSON_HEDLEY_PUBLIC +#undef JSON_HEDLEY_PURE +#undef JSON_HEDLEY_REINTERPRET_CAST +#undef JSON_HEDLEY_REQUIRE +#undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#undef JSON_HEDLEY_REQUIRE_MSG +#undef JSON_HEDLEY_RESTRICT +#undef JSON_HEDLEY_RETURNS_NON_NULL +#undef JSON_HEDLEY_SENTINEL +#undef JSON_HEDLEY_STATIC_ASSERT +#undef JSON_HEDLEY_STATIC_CAST +#undef JSON_HEDLEY_STRINGIFY +#undef JSON_HEDLEY_STRINGIFY_EX +#undef JSON_HEDLEY_SUNPRO_VERSION +#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#undef JSON_HEDLEY_TINYC_VERSION +#undef JSON_HEDLEY_TINYC_VERSION_CHECK +#undef JSON_HEDLEY_TI_ARMCL_VERSION +#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL2000_VERSION +#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL430_VERSION +#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL6X_VERSION +#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL7X_VERSION +#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CLPRU_VERSION +#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#undef JSON_HEDLEY_TI_VERSION +#undef JSON_HEDLEY_TI_VERSION_CHECK +#undef JSON_HEDLEY_UNAVAILABLE +#undef JSON_HEDLEY_UNLIKELY +#undef JSON_HEDLEY_UNPREDICTABLE +#undef JSON_HEDLEY_UNREACHABLE +#undef JSON_HEDLEY_UNREACHABLE_RETURN +#undef JSON_HEDLEY_VERSION +#undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#undef JSON_HEDLEY_VERSION_DECODE_MINOR +#undef JSON_HEDLEY_VERSION_DECODE_REVISION +#undef JSON_HEDLEY_VERSION_ENCODE +#undef JSON_HEDLEY_WARNING +#undef JSON_HEDLEY_WARN_UNUSED_RESULT +#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#undef JSON_HEDLEY_FALL_THROUGH + + + +#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/src/jwt-cpp/include/jwt-cpp/picojson.h b/dep/jwt-cpp/include/picojson/picojson.h similarity index 96% rename from src/jwt-cpp/include/jwt-cpp/picojson.h rename to dep/jwt-cpp/include/picojson/picojson.h index 24a60c5be..76742fe06 100644 --- a/src/jwt-cpp/include/jwt-cpp/picojson.h +++ b/dep/jwt-cpp/include/picojson/picojson.h @@ -76,8 +76,14 @@ extern "C" { // experimental support for int64_t (see README.mkdn for detail) #ifdef PICOJSON_USE_INT64 #define __STDC_FORMAT_MACROS -#include +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { #include +} +#endif #endif // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 @@ -104,6 +110,7 @@ extern "C" { #pragma warning(disable : 4244) // conversion from int to char #pragma warning(disable : 4127) // conditional expression is constant #pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression #else #define SNPRINTF snprintf #endif @@ -123,7 +130,7 @@ enum { #endif }; -enum { INDENT_WIDTH = 2 }; +enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; struct null {}; @@ -377,7 +384,7 @@ GET(array, *u_.array_) GET(object, *u_.object_) #ifdef PICOJSON_USE_INT64 GET(double, - (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), u_.number_)) GET(int64_t, u_.int64_) #else @@ -832,7 +839,7 @@ template inline bool _parse_object(Context &ct return false; } if (in.expect('}')) { - return true; + return ctx.parse_object_stop(); } do { std::string key; @@ -843,7 +850,7 @@ template inline bool _parse_object(Context &ct return false; } } while (in.expect(',')); - return in.expect('}'); + return in.expect('}') && ctx.parse_object_stop(); } template inline std::string _parse_number(input &in) { @@ -959,9 +966,10 @@ public: class default_parse_context { protected: value *out_; + size_t depths_; public: - default_parse_context(value *out) : out_(out) { + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { } bool set_null() { *out_ = value(); @@ -986,27 +994,37 @@ public: return _parse_string(out_->get(), in); } bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; *out_ = value(array_type, false); return true; } template bool parse_array_item(input &in, size_t) { array &a = out_->get(); a.push_back(value()); - default_parse_context ctx(&a.back()); + default_parse_context ctx(&a.back(), depths_); return _parse(ctx, in); } bool parse_array_stop(size_t) { + ++depths_; return true; } bool parse_object_start() { + if (depths_ == 0) + return false; *out_ = value(object_type, false); return true; } template bool parse_object_item(input &in, const std::string &key) { object &o = out_->get(); - default_parse_context ctx(&o[key]); + default_parse_context ctx(&o[key], depths_); return _parse(ctx, in); } + bool parse_object_stop() { + ++depths_; + return true; + } private: default_parse_context(const default_parse_context &); @@ -1014,6 +1032,9 @@ private: }; class null_parse_context { +protected: + size_t depths_; + public: struct dummy_str { void push_back(int) { @@ -1021,7 +1042,7 @@ public: }; public: - null_parse_context() { + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { } bool set_null() { return true; @@ -1042,20 +1063,31 @@ public: return _parse_string(s, in); } bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; return true; } template bool parse_array_item(input &in, size_t) { return _parse(*this, in); } bool parse_array_stop(size_t) { + ++depths_; return true; } bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; return true; } template bool parse_object_item(input &in, const std::string &) { + ++depths_; return _parse(*this, in); } + bool parse_object_stop() { + return true; + } private: null_parse_context(const null_parse_context &); @@ -1165,4 +1197,4 @@ inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { #pragma warning(pop) #endif -#endif \ No newline at end of file +#endif diff --git a/dep/libbcrypt/CMakeLists.txt b/dep/libbcrypt/CMakeLists.txt new file mode 100644 index 000000000..9885d016d --- /dev/null +++ b/dep/libbcrypt/CMakeLists.txt @@ -0,0 +1,25 @@ +enable_language(ASM) + +set(BCRYPT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/bcrypt.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_blowfish.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_gensalt.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/wrapper.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/x86.S) + +add_library(bcrypt STATIC ${BCRYPT_SOURCES}) +add_library(libbcrypt::bcrypt ALIAS bcrypt) + +target_include_directories(bcrypt + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include/bcrypt) + +target_link_libraries(bcrypt + PRIVATE + zm-dependency-interface) + +if(BSD) + target_compile_definitions(bcrypt + PRIVATE + __SKIP_GNU) +endif() diff --git a/src/libbcrypt/LICENSE b/dep/libbcrypt/LICENSE similarity index 100% rename from src/libbcrypt/LICENSE rename to dep/libbcrypt/LICENSE diff --git a/src/libbcrypt/README.md b/dep/libbcrypt/README.md similarity index 100% rename from src/libbcrypt/README.md rename to dep/libbcrypt/README.md diff --git a/src/libbcrypt/include/bcrypt/BCrypt.hpp b/dep/libbcrypt/include/bcrypt/BCrypt.hpp similarity index 96% rename from src/libbcrypt/include/bcrypt/BCrypt.hpp rename to dep/libbcrypt/include/bcrypt/BCrypt.hpp index 27f9b563e..07e5c53d4 100644 --- a/src/libbcrypt/include/bcrypt/BCrypt.hpp +++ b/dep/libbcrypt/include/bcrypt/BCrypt.hpp @@ -1,32 +1,32 @@ -#ifndef __BCRYPT__ -#define __BCRYPT__ - -#ifdef _WIN32 -#include "winbcrypt.h" -#else - -#include "bcrypt.h" -#include -#include - -class BCrypt { -public: - static std::string generateHash(const std::string & password, int workload = 12){ - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - ret = bcrypt_gensalt(workload, salt); - if(ret != 0)throw std::runtime_error{"bcrypt: can not generate salt"}; - ret = bcrypt_hashpw(password.c_str(), salt, hash); - if(ret != 0)throw std::runtime_error{"bcrypt: can not generate hash"}; - return std::string{hash}; - } - - static bool validatePassword(const std::string & password, const std::string & hash){ - return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); - } -}; - -#endif - -#endif // __BCRYPT__ +#ifndef __BCRYPT__ +#define __BCRYPT__ + +#ifdef _WIN32 +#include "winbcrypt.h" +#else + +#include "bcrypt.h" +#include +#include + +class BCrypt { +public: + static std::string generateHash(const std::string & password, int workload = 12){ + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + ret = bcrypt_gensalt(workload, salt); + if(ret != 0)throw std::runtime_error{"bcrypt: can not generate salt"}; + ret = bcrypt_hashpw(password.c_str(), salt, hash); + if(ret != 0)throw std::runtime_error{"bcrypt: can not generate hash"}; + return std::string{hash}; + } + + static bool validatePassword(const std::string & password, const std::string & hash){ + return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); + } +}; + +#endif + +#endif // __BCRYPT__ diff --git a/src/libbcrypt/include/bcrypt/bcrypt.h b/dep/libbcrypt/include/bcrypt/bcrypt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/bcrypt.h rename to dep/libbcrypt/include/bcrypt/bcrypt.h diff --git a/src/libbcrypt/include/bcrypt/crypt.h b/dep/libbcrypt/include/bcrypt/crypt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt.h rename to dep/libbcrypt/include/bcrypt/crypt.h diff --git a/src/libbcrypt/include/bcrypt/crypt_blowfish.h b/dep/libbcrypt/include/bcrypt/crypt_blowfish.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt_blowfish.h rename to dep/libbcrypt/include/bcrypt/crypt_blowfish.h diff --git a/src/libbcrypt/include/bcrypt/crypt_gensalt.h b/dep/libbcrypt/include/bcrypt/crypt_gensalt.h similarity index 100% rename from src/libbcrypt/include/bcrypt/crypt_gensalt.h rename to dep/libbcrypt/include/bcrypt/crypt_gensalt.h diff --git a/src/libbcrypt/include/bcrypt/ow-crypt.h b/dep/libbcrypt/include/bcrypt/ow-crypt.h similarity index 97% rename from src/libbcrypt/include/bcrypt/ow-crypt.h rename to dep/libbcrypt/include/bcrypt/ow-crypt.h index 2c8ddf8f6..2e4879426 100644 --- a/src/libbcrypt/include/bcrypt/ow-crypt.h +++ b/dep/libbcrypt/include/bcrypt/ow-crypt.h @@ -1,43 +1,43 @@ -/* - * Written by Solar Designer in 2000-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - */ - -#ifndef _OW_CRYPT_H -#define _OW_CRYPT_H - -#ifndef __GNUC__ -#undef __const -#define __const const -#endif - -#ifndef __SKIP_GNU -extern char *crypt(__const char *key, __const char *setting); -extern char *crypt_r(__const char *key, __const char *setting, void *data); -#endif - -#ifndef __SKIP_OW -extern char *crypt_rn(__const char *key, __const char *setting, - void *data, int size); -extern char *crypt_ra(__const char *key, __const char *setting, - void **data, int *size); -extern char *crypt_gensalt(__const char *prefix, unsigned long count, - __const char *input, int size); -extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count, - __const char *input, int size, char *output, int output_size); -extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count, - __const char *input, int size); -#endif - -#endif +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _OW_CRYPT_H +#define _OW_CRYPT_H + +#ifndef __GNUC__ +#undef __const +#define __const const +#endif + +#ifndef __SKIP_GNU +extern char *crypt(__const char *key, __const char *setting); +extern char *crypt_r(__const char *key, __const char *setting, void *data); +#endif + +#ifndef __SKIP_OW +extern char *crypt_rn(__const char *key, __const char *setting, + void *data, int size); +extern char *crypt_ra(__const char *key, __const char *setting, + void **data, int *size); +extern char *crypt_gensalt(__const char *prefix, unsigned long count, + __const char *input, int size); +extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count, + __const char *input, int size, char *output, int output_size); +extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count, + __const char *input, int size); +#endif + +#endif diff --git a/src/libbcrypt/include/bcrypt/winbcrypt.h b/dep/libbcrypt/include/bcrypt/winbcrypt.h similarity index 95% rename from src/libbcrypt/include/bcrypt/winbcrypt.h rename to dep/libbcrypt/include/bcrypt/winbcrypt.h index 703ecd211..8ac27e1c3 100644 --- a/src/libbcrypt/include/bcrypt/winbcrypt.h +++ b/dep/libbcrypt/include/bcrypt/winbcrypt.h @@ -1,27 +1,27 @@ -#ifndef __WIN_BCRYPT__H -#define __WIN_BCRYPT__H - -#include - -#include "crypt_blowfish.h" -#include "./bcrypt.h" - -class BCrypt { -public: - static std::string generateHash(const std::string & password, int workload = 12) { - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - ret = bcrypt_gensalt(workload, salt); - if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate salt" }; - ret = bcrypt_hashpw(password.c_str(), salt, hash); - if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate hash" }; - return std::string{ hash }; - } - - static bool validatePassword(const std::string & password, const std::string & hash) { - return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); - } -}; - +#ifndef __WIN_BCRYPT__H +#define __WIN_BCRYPT__H + +#include + +#include "crypt_blowfish.h" +#include "./bcrypt.h" + +class BCrypt { +public: + static std::string generateHash(const std::string & password, int workload = 12) { + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + ret = bcrypt_gensalt(workload, salt); + if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate salt" }; + ret = bcrypt_hashpw(password.c_str(), salt, hash); + if (ret != 0)throw std::runtime_error{ "bcrypt: can not generate hash" }; + return std::string{ hash }; + } + + static bool validatePassword(const std::string & password, const std::string & hash) { + return (bcrypt_checkpw(password.c_str(), hash.c_str()) == 0); + } +}; + #endif \ No newline at end of file diff --git a/src/libbcrypt/src/bcrypt.c b/dep/libbcrypt/src/bcrypt.c similarity index 95% rename from src/libbcrypt/src/bcrypt.c rename to dep/libbcrypt/src/bcrypt.c index db16d27b3..eafdb2136 100644 --- a/src/libbcrypt/src/bcrypt.c +++ b/dep/libbcrypt/src/bcrypt.c @@ -1,230 +1,230 @@ -/* - * bcrypt wrapper library - * - * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia - * - * To the extent possible under law, the author(s) have dedicated all copyright - * and related and neighboring rights to this software to the public domain - * worldwide. This software is distributed without any warranty. - * - * You should have received a copy of the CC0 Public Domain Dedication along - * with this software. If not, see - * . - */ -#include -#include -#include -#include -#ifdef _WIN32 -#elif _WIN64 -#else -#include -#endif -#include - -#if defined(_WIN32) || defined(_WIN64) -// On windows we need to generate random bytes differently. -typedef __int64 ssize_t; -#define BCRYPT_HASHSIZE 60 - -#include "../include/bcrypt/bcrypt.h" - -#include -#include /* CryptAcquireContext, CryptGenRandom */ -#else -#include "bcrypt.h" -#include "ow-crypt.h" -#endif - -#define RANDBYTES (16) - -static int try_close(int fd) -{ - int ret; - for (;;) { - errno = 0; - ret = close(fd); - if (ret == -1 && errno == EINTR) - continue; - break; - } - return ret; -} - -static int try_read(int fd, char *out, size_t count) -{ - size_t total; - ssize_t partial; - - total = 0; - while (total < count) - { - for (;;) { - errno = 0; - partial = read(fd, out + total, count - total); - if (partial == -1 && errno == EINTR) - continue; - break; - } - - if (partial < 1) - return -1; - - total += partial; - } - - return 0; -} - -/* - * This is a best effort implementation. Nothing prevents a compiler from - * optimizing this function and making it vulnerable to timing attacks, but - * this method is commonly used in crypto libraries like NaCl. - * - * Return value is zero if both strings are equal and nonzero otherwise. -*/ -static int timing_safe_strcmp(const char *str1, const char *str2) -{ - const unsigned char *u1; - const unsigned char *u2; - int ret; - int i; - - int len1 = strlen(str1); - int len2 = strlen(str2); - - /* In our context both strings should always have the same length - * because they will be hashed passwords. */ - if (len1 != len2) - return 1; - - /* Force unsigned for bitwise operations. */ - u1 = (const unsigned char *)str1; - u2 = (const unsigned char *)str2; - - ret = 0; - for (i = 0; i < len1; ++i) - ret |= (u1[i] ^ u2[i]); - - return ret; -} - -int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE]) -{ - int fd; - char input[RANDBYTES]; - int workf; - char *aux; - - // Note: Windows does not have /dev/urandom sadly. -#if defined(_WIN32) || defined(_WIN64) - HCRYPTPROV p; - ULONG i; - - // Acquire a crypt context for generating random bytes. - if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) { - return 1; - } - - if (CryptGenRandom(p, RANDBYTES, (BYTE*)input) == FALSE) { - return 2; - } - - if (CryptReleaseContext(p, 0) == FALSE) { - return 3; - } -#else - // Get random bytes on Unix/Linux. - fd = open("/dev/urandom", O_RDONLY); - if (fd == -1) - return 1; - - if (try_read(fd, input, RANDBYTES) != 0) { - if (try_close(fd) != 0) - return 4; - return 2; - } - - if (try_close(fd) != 0) - return 3; -#endif - - /* Generate salt. */ - workf = (factor < 4 || factor > 31)?12:factor; - aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES, - salt, BCRYPT_HASHSIZE); - return (aux == NULL)?5:0; -} - -int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) -{ - char *aux; - aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE); - return (aux == NULL)?1:0; -} - -int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]) -{ - int ret; - char outhash[BCRYPT_HASHSIZE]; - - ret = bcrypt_hashpw(passwd, hash, outhash); - if (ret != 0) - return -1; - - return timing_safe_strcmp(hash, outhash); -} - -#ifdef TEST_BCRYPT -#include -#include -#include -#include - -int main(void) -{ - clock_t before; - clock_t after; - char salt[BCRYPT_HASHSIZE]; - char hash[BCRYPT_HASHSIZE]; - int ret; - - const char pass[] = "hi,mom"; - const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK"; - const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO"; - - ret = bcrypt_gensalt(12, salt); - assert(ret == 0); - printf("Generated salt: %s\n", salt); - before = clock(); - ret = bcrypt_hashpw("testtesttest", salt, hash); - assert(ret == 0); - after = clock(); - printf("Hashed password: %s\n", hash); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - ret = bcrypt_hashpw(pass, hash1, hash); - assert(ret == 0); - printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL"); - ret = bcrypt_hashpw(pass, hash2, hash); - assert(ret == 0); - printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL"); - - before = clock(); - ret = (bcrypt_checkpw(pass, hash1) == 0); - after = clock(); - printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - before = clock(); - ret = (bcrypt_checkpw(pass, hash2) == 0); - after = clock(); - printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); - printf("Time taken: %f seconds\n", - (double)(after - before) / CLOCKS_PER_SEC); - - return 0; -} -#endif +/* + * bcrypt wrapper library + * + * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ +#include +#include +#include +#include +#ifdef _WIN32 +#elif _WIN64 +#else +#include +#endif +#include + +#if defined(_WIN32) || defined(_WIN64) +// On windows we need to generate random bytes differently. +typedef __int64 ssize_t; +#define BCRYPT_HASHSIZE 60 + +#include "../include/bcrypt/bcrypt.h" + +#include +#include /* CryptAcquireContext, CryptGenRandom */ +#else +#include "bcrypt.h" +#include "ow-crypt.h" +#endif + +#define RANDBYTES (16) + +static int try_close(int fd) +{ + int ret; + for (;;) { + errno = 0; + ret = close(fd); + if (ret == -1 && errno == EINTR) + continue; + break; + } + return ret; +} + +static int try_read(int fd, char *out, size_t count) +{ + size_t total; + ssize_t partial; + + total = 0; + while (total < count) + { + for (;;) { + errno = 0; + partial = read(fd, out + total, count - total); + if (partial == -1 && errno == EINTR) + continue; + break; + } + + if (partial < 1) + return -1; + + total += partial; + } + + return 0; +} + +/* + * This is a best effort implementation. Nothing prevents a compiler from + * optimizing this function and making it vulnerable to timing attacks, but + * this method is commonly used in crypto libraries like NaCl. + * + * Return value is zero if both strings are equal and nonzero otherwise. +*/ +static int timing_safe_strcmp(const char *str1, const char *str2) +{ + const unsigned char *u1; + const unsigned char *u2; + int ret; + int i; + + int len1 = strlen(str1); + int len2 = strlen(str2); + + /* In our context both strings should always have the same length + * because they will be hashed passwords. */ + if (len1 != len2) + return 1; + + /* Force unsigned for bitwise operations. */ + u1 = (const unsigned char *)str1; + u2 = (const unsigned char *)str2; + + ret = 0; + for (i = 0; i < len1; ++i) + ret |= (u1[i] ^ u2[i]); + + return ret; +} + +int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE]) +{ + int fd; + char input[RANDBYTES]; + int workf; + char *aux; + + // Note: Windows does not have /dev/urandom sadly. +#if defined(_WIN32) || defined(_WIN64) + HCRYPTPROV p; + ULONG i; + + // Acquire a crypt context for generating random bytes. + if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) { + return 1; + } + + if (CryptGenRandom(p, RANDBYTES, (BYTE*)input) == FALSE) { + return 2; + } + + if (CryptReleaseContext(p, 0) == FALSE) { + return 3; + } +#else + // Get random bytes on Unix/Linux. + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + return 1; + + if (try_read(fd, input, RANDBYTES) != 0) { + if (try_close(fd) != 0) + return 4; + return 2; + } + + if (try_close(fd) != 0) + return 3; +#endif + + /* Generate salt. */ + workf = (factor < 4 || factor > 31)?12:factor; + aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES, + salt, BCRYPT_HASHSIZE); + return (aux == NULL)?5:0; +} + +int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) +{ + char *aux; + aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE); + return (aux == NULL)?1:0; +} + +int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]) +{ + int ret; + char outhash[BCRYPT_HASHSIZE]; + + ret = bcrypt_hashpw(passwd, hash, outhash); + if (ret != 0) + return -1; + + return timing_safe_strcmp(hash, outhash); +} + +#ifdef TEST_BCRYPT +#include +#include +#include +#include + +int main(void) +{ + clock_t before; + clock_t after; + char salt[BCRYPT_HASHSIZE]; + char hash[BCRYPT_HASHSIZE]; + int ret; + + const char pass[] = "hi,mom"; + const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK"; + const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO"; + + ret = bcrypt_gensalt(12, salt); + assert(ret == 0); + printf("Generated salt: %s\n", salt); + before = clock(); + ret = bcrypt_hashpw("testtesttest", salt, hash); + assert(ret == 0); + after = clock(); + printf("Hashed password: %s\n", hash); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + ret = bcrypt_hashpw(pass, hash1, hash); + assert(ret == 0); + printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL"); + ret = bcrypt_hashpw(pass, hash2, hash); + assert(ret == 0); + printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL"); + + before = clock(); + ret = (bcrypt_checkpw(pass, hash1) == 0); + after = clock(); + printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + before = clock(); + ret = (bcrypt_checkpw(pass, hash2) == 0); + after = clock(); + printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL"); + printf("Time taken: %f seconds\n", + (double)(after - before) / CLOCKS_PER_SEC); + + return 0; +} +#endif diff --git a/src/libbcrypt/src/crypt_blowfish.c b/dep/libbcrypt/src/crypt_blowfish.c similarity index 97% rename from src/libbcrypt/src/crypt_blowfish.c rename to dep/libbcrypt/src/crypt_blowfish.c index 0fa55ea07..27133d535 100644 --- a/src/libbcrypt/src/crypt_blowfish.c +++ b/dep/libbcrypt/src/crypt_blowfish.c @@ -1,911 +1,911 @@ -/* - * The crypt_blowfish homepage is: - * - * http://www.openwall.com/crypt/ - * - * This code comes from John the Ripper password cracker, with reentrant - * and crypt(3) interfaces added, but optimizations specific to password - * cracking removed. - * - * Written by Solar Designer in 1998-2014. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * It is my intent that you should be able to use this on your system, - * as part of a software package, or anywhere else to improve security, - * ensure compatibility, or for any other purpose. I would appreciate - * it if you give credit where it is due and keep your modifications in - * the public domain as well, but I don't require that in order to let - * you place this code and any modifications you make under a license - * of your choice. - * - * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix - * "$2b$", originally by Niels Provos , and it uses - * some of his ideas. The password hashing algorithm was designed by David - * Mazieres . For information on the level of - * compatibility for bcrypt hash prefixes other than "$2b$", please refer to - * the comments in BF_set_key() below and to the included crypt(3) man page. - * - * There's a paper on the algorithm that explains its design decisions: - * - * http://www.usenix.org/events/usenix99/provos.html - * - * Some of the tricks in BF_ROUND might be inspired by Eric Young's - * Blowfish library (I can't be sure if I would think of something if I - * hadn't seen his code). - */ - -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -/* Just to make sure the prototypes match the actual definitions */ -#ifdef _WIN32 || _WIN64 -#include "../include/bcrypt/crypt_blowfish.h" -#else -#include "crypt_blowfish.h" -#endif - -#ifdef __i386__ -#define BF_ASM 1 -#define BF_SCALE 1 -#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) -#define BF_ASM 0 -#define BF_SCALE 1 -#else -#define BF_ASM 0 -#define BF_SCALE 0 -#endif - -typedef unsigned int BF_word; -typedef signed int BF_word_signed; - -/* Number of Blowfish rounds, this is also hardcoded into a few places */ -#define BF_N 16 - -typedef BF_word BF_key[BF_N + 2]; - -typedef struct { - BF_word S[4][0x100]; - BF_key P; -} BF_ctx; - -/* - * Magic IV for 64 Blowfish encryptions that we do at the end. - * The string is "OrpheanBeholderScryDoubt" on big-endian. - */ -static BF_word BF_magic_w[6] = { - 0x4F727068, 0x65616E42, 0x65686F6C, - 0x64657253, 0x63727944, 0x6F756274 -}; - -/* - * P-box and S-box tables initialized with digits of Pi. - */ -static BF_ctx BF_init_state = { - { - { - 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, - 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, - 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, - 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, - 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, - 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, - 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, - 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, - 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, - 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, - 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, - 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, - 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, - 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, - 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, - 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, - 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, - 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, - 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, - 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, - 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, - 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, - 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, - 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, - 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, - 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, - 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, - 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, - 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, - 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, - 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, - 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, - 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, - 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, - 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, - 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, - 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, - 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, - 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, - 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, - 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, - 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, - 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, - 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, - 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, - 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, - 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, - 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, - 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, - 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, - 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, - 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, - 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, - 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, - 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, - 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, - 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, - 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, - 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, - 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, - 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, - 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, - 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, - 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a - }, { - 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, - 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, - 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, - 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, - 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, - 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, - 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, - 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, - 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, - 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, - 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, - 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, - 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, - 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, - 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, - 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, - 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, - 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, - 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, - 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, - 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, - 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, - 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, - 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, - 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, - 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, - 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, - 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, - 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, - 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, - 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, - 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, - 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, - 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, - 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, - 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, - 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, - 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, - 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, - 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, - 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, - 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, - 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, - 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, - 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, - 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, - 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, - 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, - 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, - 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, - 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, - 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, - 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, - 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, - 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, - 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, - 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, - 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, - 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, - 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, - 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, - 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, - 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, - 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 - }, { - 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, - 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, - 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, - 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, - 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, - 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, - 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, - 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, - 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, - 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, - 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, - 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, - 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, - 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, - 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, - 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, - 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, - 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, - 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, - 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, - 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, - 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, - 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, - 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, - 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, - 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, - 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, - 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, - 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, - 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, - 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, - 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, - 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, - 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, - 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, - 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, - 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, - 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, - 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, - 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, - 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, - 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, - 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, - 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, - 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, - 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, - 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, - 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, - 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, - 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, - 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, - 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, - 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, - 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, - 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, - 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, - 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, - 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, - 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, - 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, - 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, - 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, - 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, - 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 - }, { - 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, - 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, - 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, - 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, - 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, - 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, - 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, - 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, - 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, - 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, - 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, - 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, - 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, - 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, - 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, - 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, - 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, - 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, - 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, - 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, - 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, - 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, - 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, - 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, - 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, - 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, - 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, - 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, - 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, - 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, - 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, - 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, - 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, - 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, - 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, - 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, - 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, - 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, - 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, - 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, - 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, - 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, - 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, - 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, - 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, - 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, - 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, - 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, - 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, - 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, - 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, - 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, - 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, - 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, - 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, - 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, - 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, - 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, - 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, - 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, - 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, - 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, - 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, - 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 - } - }, { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, - 0x9216d5d9, 0x8979fb1b - } -}; - -static unsigned char BF_itoa64[64 + 1] = - "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -static unsigned char BF_atoi64[0x60] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, - 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, - 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 -}; - -#define BF_safe_atoi64(dst, src) \ -{ \ - tmp = (unsigned char)(src); \ - if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ - tmp = BF_atoi64[tmp]; \ - if (tmp > 63) return -1; \ - (dst) = tmp; \ -} - -static int BF_decode(BF_word *dst, const char *src, int size) -{ - unsigned char *dptr = (unsigned char *)dst; - unsigned char *end = dptr + size; - const unsigned char *sptr = (const unsigned char *)src; - unsigned int tmp, c1, c2, c3, c4; - - do { - BF_safe_atoi64(c1, *sptr++); - BF_safe_atoi64(c2, *sptr++); - *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); - if (dptr >= end) break; - - BF_safe_atoi64(c3, *sptr++); - *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); - if (dptr >= end) break; - - BF_safe_atoi64(c4, *sptr++); - *dptr++ = ((c3 & 0x03) << 6) | c4; - } while (dptr < end); - - return 0; -} - -static void BF_encode(char *dst, const BF_word *src, int size) -{ - const unsigned char *sptr = (const unsigned char *)src; - const unsigned char *end = sptr + size; - unsigned char *dptr = (unsigned char *)dst; - unsigned int c1, c2; - - do { - c1 = *sptr++; - *dptr++ = BF_itoa64[c1 >> 2]; - c1 = (c1 & 0x03) << 4; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 4; - *dptr++ = BF_itoa64[c1]; - c1 = (c2 & 0x0f) << 2; - if (sptr >= end) { - *dptr++ = BF_itoa64[c1]; - break; - } - - c2 = *sptr++; - c1 |= c2 >> 6; - *dptr++ = BF_itoa64[c1]; - *dptr++ = BF_itoa64[c2 & 0x3f]; - } while (sptr < end); -} - -static void BF_swap(BF_word *x, int count) -{ - static int endianness_check = 1; - char *is_little_endian = (char *)&endianness_check; - BF_word tmp; - - if (*is_little_endian) - do { - tmp = *x; - tmp = (tmp << 16) | (tmp >> 16); - *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); - } while (--count); -} - -#if BF_SCALE -/* Architectures which can shift addresses left by 2 bits with no extra cost */ -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp2 = L >> 8; \ - tmp2 &= 0xFF; \ - tmp3 = L >> 16; \ - tmp3 &= 0xFF; \ - tmp4 = L >> 24; \ - tmp1 = data.ctx.S[3][tmp1]; \ - tmp2 = data.ctx.S[2][tmp2]; \ - tmp3 = data.ctx.S[1][tmp3]; \ - tmp3 += data.ctx.S[0][tmp4]; \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#else -/* Architectures with no complicated addressing modes supported */ -#define BF_INDEX(S, i) \ - (*((BF_word *)(((unsigned char *)S) + (i)))) -#define BF_ROUND(L, R, N) \ - tmp1 = L & 0xFF; \ - tmp1 <<= 2; \ - tmp2 = L >> 6; \ - tmp2 &= 0x3FC; \ - tmp3 = L >> 14; \ - tmp3 &= 0x3FC; \ - tmp4 = L >> 22; \ - tmp4 &= 0x3FC; \ - tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ - tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ - tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ - tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ - tmp3 ^= tmp2; \ - R ^= data.ctx.P[N + 1]; \ - tmp3 += tmp1; \ - R ^= tmp3; -#endif - -/* - * Encrypt one block, BF_N is hardcoded here. - */ -#define BF_ENCRYPT \ - L ^= data.ctx.P[0]; \ - BF_ROUND(L, R, 0); \ - BF_ROUND(R, L, 1); \ - BF_ROUND(L, R, 2); \ - BF_ROUND(R, L, 3); \ - BF_ROUND(L, R, 4); \ - BF_ROUND(R, L, 5); \ - BF_ROUND(L, R, 6); \ - BF_ROUND(R, L, 7); \ - BF_ROUND(L, R, 8); \ - BF_ROUND(R, L, 9); \ - BF_ROUND(L, R, 10); \ - BF_ROUND(R, L, 11); \ - BF_ROUND(L, R, 12); \ - BF_ROUND(R, L, 13); \ - BF_ROUND(L, R, 14); \ - BF_ROUND(R, L, 15); \ - tmp4 = R; \ - R = L; \ - L = tmp4 ^ data.ctx.P[BF_N + 1]; - -#if BF_ASM -#define BF_body() \ - _BF_body_r(&data.ctx); -#else -#define BF_body() \ - L = R = 0; \ - ptr = data.ctx.P; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.P[BF_N + 2]); \ -\ - ptr = data.ctx.S[0]; \ - do { \ - ptr += 2; \ - BF_ENCRYPT; \ - *(ptr - 2) = L; \ - *(ptr - 1) = R; \ - } while (ptr < &data.ctx.S[3][0xFF]); -#endif - -static void BF_set_key(const char *key, BF_key expanded, BF_key initial, - unsigned char flags) -{ - const char *ptr = key; - unsigned int bug, i, j; - BF_word safety, sign, diff, tmp[2]; - -/* - * There was a sign extension bug in older revisions of this function. While - * we would have liked to simply fix the bug and move on, we have to provide - * a backwards compatibility feature (essentially the bug) for some systems and - * a safety measure for some others. The latter is needed because for certain - * multiple inputs to the buggy algorithm there exist easily found inputs to - * the correct algorithm that produce the same hash. Thus, we optionally - * deviate from the correct algorithm just enough to avoid such collisions. - * While the bug itself affected the majority of passwords containing - * characters with the 8th bit set (although only a percentage of those in a - * collision-producing way), the anti-collision safety measure affects - * only a subset of passwords containing the '\xff' character (not even all of - * those passwords, just some of them). This character is not found in valid - * UTF-8 sequences and is rarely used in popular 8-bit character encodings. - * Thus, the safety measure is unlikely to cause much annoyance, and is a - * reasonable tradeoff to use when authenticating against existing hashes that - * are not reliably known to have been computed with the correct algorithm. - * - * We use an approach that tries to minimize side-channel leaks of password - * information - that is, we mostly use fixed-cost bitwise operations instead - * of branches or table lookups. (One conditional branch based on password - * length remains. It is not part of the bug aftermath, though, and is - * difficult and possibly unreasonable to avoid given the use of C strings by - * the caller, which results in similar timing leaks anyway.) - * - * For actual implementation, we set an array index in the variable "bug" - * (0 means no bug, 1 means sign extension bug emulation) and a flag in the - * variable "safety" (bit 16 is set when the safety measure is requested). - * Valid combinations of settings are: - * - * Prefix "$2a$": bug = 0, safety = 0x10000 - * Prefix "$2b$": bug = 0, safety = 0 - * Prefix "$2x$": bug = 1, safety = 0 - * Prefix "$2y$": bug = 0, safety = 0 - */ - bug = (unsigned int)flags & 1; - safety = ((BF_word)flags & 2) << 15; - - sign = diff = 0; - - for (i = 0; i < BF_N + 2; i++) { - tmp[0] = tmp[1] = 0; - for (j = 0; j < 4; j++) { - tmp[0] <<= 8; - tmp[0] |= (unsigned char)*ptr; /* correct */ - tmp[1] <<= 8; - tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ -/* - * Sign extension in the first char has no effect - nothing to overwrite yet, - * and those extra 24 bits will be fully shifted out of the 32-bit word. For - * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign - * extension in tmp[1] occurs. Once this flag is set, it remains set. - */ - if (j) - sign |= tmp[1] & 0x80; - if (!*ptr) - ptr = key; - else - ptr++; - } - diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ - - expanded[i] = tmp[bug]; - initial[i] = BF_init_state.P[i] ^ tmp[bug]; - } - -/* - * At this point, "diff" is zero iff the correct and buggy algorithms produced - * exactly the same result. If so and if "sign" is non-zero, which indicates - * that there was a non-benign sign extension, this means that we have a - * collision between the correctly computed hash for this password and a set of - * passwords that could be supplied to the buggy algorithm. Our safety measure - * is meant to protect from such many-buggy to one-correct collisions, by - * deviating from the correct algorithm in such cases. Let's check for this. - */ - diff |= diff >> 16; /* still zero iff exact match */ - diff &= 0xffff; /* ditto */ - diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ - sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ - sign &= ~diff & safety; /* action needed? */ - -/* - * If we have determined that we need to deviate from the correct algorithm, - * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but - * let's stick to it now. It came out of the approach we used above, and it's - * not any worse than any other choice we could make.) - * - * It is crucial that we don't do the same to the expanded key used in the main - * Eksblowfish loop. By doing it to only one of these two, we deviate from a - * state that could be directly specified by a password to the buggy algorithm - * (and to the fully correct one as well, but that's a side-effect). - */ - initial[0] ^= sign; -} - -static const unsigned char flags_by_subtype[26] = - {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; - -static char *BF_crypt(const char *key, const char *setting, - char *output, int size, - BF_word min) -{ -#if BF_ASM - extern void _BF_body_r(BF_ctx *ctx); -#endif - struct { - BF_ctx ctx; - BF_key expanded_key; - union { - BF_word salt[4]; - BF_word output[6]; - } binary; - } data; - BF_word L, R; - BF_word tmp1, tmp2, tmp3, tmp4; - BF_word *ptr; - BF_word count; - int i; - - if (size < 7 + 22 + 31 + 1) { - __set_errno(ERANGE); - return NULL; - } - - if (setting[0] != '$' || - setting[1] != '2' || - setting[2] < 'a' || setting[2] > 'z' || - !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || - setting[3] != '$' || - setting[4] < '0' || setting[4] > '3' || - setting[5] < '0' || setting[5] > '9' || - (setting[4] == '3' && setting[5] > '1') || - setting[6] != '$') { - __set_errno(EINVAL); - return NULL; - } - - count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); - if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { - __set_errno(EINVAL); - return NULL; - } - BF_swap(data.binary.salt, 4); - - BF_set_key(key, data.expanded_key, data.ctx.P, - flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); - - memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); - - L = R = 0; - for (i = 0; i < BF_N + 2; i += 2) { - L ^= data.binary.salt[i & 2]; - R ^= data.binary.salt[(i & 2) + 1]; - BF_ENCRYPT; - data.ctx.P[i] = L; - data.ctx.P[i + 1] = R; - } - - ptr = data.ctx.S[0]; - do { - ptr += 4; - L ^= data.binary.salt[(BF_N + 2) & 3]; - R ^= data.binary.salt[(BF_N + 3) & 3]; - BF_ENCRYPT; - *(ptr - 4) = L; - *(ptr - 3) = R; - - L ^= data.binary.salt[(BF_N + 4) & 3]; - R ^= data.binary.salt[(BF_N + 5) & 3]; - BF_ENCRYPT; - *(ptr - 2) = L; - *(ptr - 1) = R; - } while (ptr < &data.ctx.S[3][0xFF]); - - do { - int done; - - for (i = 0; i < BF_N + 2; i += 2) { - data.ctx.P[i] ^= data.expanded_key[i]; - data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; - } - - done = 0; - do { - BF_body(); - if (done) - break; - done = 1; - - tmp1 = data.binary.salt[0]; - tmp2 = data.binary.salt[1]; - tmp3 = data.binary.salt[2]; - tmp4 = data.binary.salt[3]; - for (i = 0; i < BF_N; i += 4) { - data.ctx.P[i] ^= tmp1; - data.ctx.P[i + 1] ^= tmp2; - data.ctx.P[i + 2] ^= tmp3; - data.ctx.P[i + 3] ^= tmp4; - } - data.ctx.P[16] ^= tmp1; - data.ctx.P[17] ^= tmp2; - } while (1); - } while (--count); - - for (i = 0; i < 6; i += 2) { - L = BF_magic_w[i]; - R = BF_magic_w[i + 1]; - - count = 64; - do { - BF_ENCRYPT; - } while (--count); - - data.binary.output[i] = L; - data.binary.output[i + 1] = R; - } - - memcpy(output, setting, 7 + 22 - 1); - output[7 + 22 - 1] = BF_itoa64[(int) - BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; - -/* This has to be bug-compatible with the original implementation, so - * only encode 23 of the 24 bytes. :-) */ - BF_swap(data.binary.output, 6); - BF_encode(&output[7 + 22], data.binary.output, 23); - output[7 + 22 + 31] = '\0'; - - return output; -} - -int _crypt_output_magic(const char *setting, char *output, int size) -{ - if (size < 3) - return -1; - - output[0] = '*'; - output[1] = '0'; - output[2] = '\0'; - - if (setting[0] == '*' && setting[1] == '0') - output[1] = '1'; - - return 0; -} - -/* - * Please preserve the runtime self-test. It serves two purposes at once: - * - * 1. We really can't afford the risk of producing incompatible hashes e.g. - * when there's something like gcc bug 26587 again, whereas an application or - * library integrating this code might not also integrate our external tests or - * it might not run them after every build. Even if it does, the miscompile - * might only occur on the production build, but not on a testing build (such - * as because of different optimization settings). It is painful to recover - * from incorrectly-computed hashes - merely fixing whatever broke is not - * enough. Thus, a proactive measure like this self-test is needed. - * - * 2. We don't want to leave sensitive data from our actual password hash - * computation on the stack or in registers. Previous revisions of the code - * would do explicit cleanups, but simply running the self-test after hash - * computation is more reliable. - * - * The performance cost of this quick self-test is around 0.6% at the "$2a$08" - * setting. - */ -char *_crypt_blowfish_rn(const char *key, const char *setting, - char *output, int size) -{ - const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; - const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; - static const char * const test_hashes[2] = - {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ - "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ - const char *test_hash = test_hashes[0]; - char *retval; - const char *p; - int save_errno, ok; - struct { - char s[7 + 22 + 1]; - char o[7 + 22 + 31 + 1 + 1 + 1]; - } buf; - -/* Hash the supplied password */ - _crypt_output_magic(setting, output, size); - retval = BF_crypt(key, setting, output, size, 16); - save_errno = errno; - -/* - * Do a quick self-test. It is important that we make both calls to BF_crypt() - * from the same scope such that they likely use the same stack locations, - * which makes the second call overwrite the first call's sensitive data on the - * stack and makes it more likely that any alignment related issues would be - * detected by the self-test. - */ - memcpy(buf.s, test_setting, sizeof(buf.s)); - if (retval) { - unsigned int flags = flags_by_subtype[ - (unsigned int)(unsigned char)setting[2] - 'a']; - test_hash = test_hashes[flags & 1]; - buf.s[2] = setting[2]; - } - memset(buf.o, 0x55, sizeof(buf.o)); - buf.o[sizeof(buf.o) - 1] = 0; - p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); - - ok = (p == buf.o && - !memcmp(p, buf.s, 7 + 22) && - !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); - - { - const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; - BF_key ae, ai, ye, yi; - BF_set_key(k, ae, ai, 2); /* $2a$ */ - BF_set_key(k, ye, yi, 4); /* $2y$ */ - ai[0] ^= 0x10000; /* undo the safety (for comparison) */ - ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && - !memcmp(ae, ye, sizeof(ae)) && - !memcmp(ai, yi, sizeof(ai)); - } - - __set_errno(save_errno); - if (ok) - return retval; - -/* Should not happen */ - _crypt_output_magic(setting, output, size); - __set_errno(EINVAL); /* pretend we don't support this hash type */ - return NULL; -} - -char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - if (size < 16 || output_size < 7 + 22 + 1 || - (count && (count < 4 || count > 31)) || - prefix[0] != '$' || prefix[1] != '2' || - (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); - return NULL; - } - - if (!count) count = 5; - - output[0] = '$'; - output[1] = '2'; - output[2] = prefix[2]; - output[3] = '$'; - output[4] = '0' + count / 10; - output[5] = '0' + count % 10; - output[6] = '$'; - - BF_encode(&output[7], (const BF_word *)input, 16); - output[7 + 22] = '\0'; - - return output; -} +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#ifdef _WIN32 || _WIN64 +#include "../include/bcrypt/crypt_blowfish.h" +#else +#include "crypt_blowfish.h" +#endif + +#ifdef __i386__ +#define BF_ASM 1 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} diff --git a/src/libbcrypt/src/crypt_gensalt.c b/dep/libbcrypt/src/crypt_gensalt.c similarity index 96% rename from src/libbcrypt/src/crypt_gensalt.c rename to dep/libbcrypt/src/crypt_gensalt.c index 1f0d73da0..a1d719ec4 100644 --- a/src/libbcrypt/src/crypt_gensalt.c +++ b/dep/libbcrypt/src/crypt_gensalt.c @@ -1,128 +1,128 @@ -/* - * Written by Solar Designer in 2000-2011. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - * - * This file contains salt generation functions for the traditional and - * other common crypt(3) algorithms, except for bcrypt which is defined - * entirely in crypt_blowfish.c. - */ - -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -/* Just to make sure the prototypes match the actual definitions */ -#ifdef _WIN32 -#include "../include/bcrypt/crypt_gensalt.h" -#else -#include "crypt_gensalt.h" -#endif - -unsigned char _crypt_itoa64[64 + 1] = - "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - (void) prefix; - - if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL); - return NULL; - } - - output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f]; - output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f]; - output[2] = '\0'; - - return output; -} - -char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - unsigned long value; - - (void) prefix; - -/* Even iteration counts make it easier to detect weak DES keys from a look - * at the hash, so they should be avoided */ - if (size < 3 || output_size < 1 + 4 + 4 + 1 || - (count && (count > 0xffffff || !(count & 1)))) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL); - return NULL; - } - - if (!count) count = 725; - - output[0] = '_'; - output[1] = _crypt_itoa64[count & 0x3f]; - output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; - output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; - output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; - value = (unsigned long)(unsigned char)input[0] | - ((unsigned long)(unsigned char)input[1] << 8) | - ((unsigned long)(unsigned char)input[2] << 16); - output[5] = _crypt_itoa64[value & 0x3f]; - output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[9] = '\0'; - - return output; -} - -char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - unsigned long value; - - (void) prefix; - - if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) { - if (output_size > 0) output[0] = '\0'; - __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL); - return NULL; - } - - output[0] = '$'; - output[1] = '1'; - output[2] = '$'; - value = (unsigned long)(unsigned char)input[0] | - ((unsigned long)(unsigned char)input[1] << 8) | - ((unsigned long)(unsigned char)input[2] << 16); - output[3] = _crypt_itoa64[value & 0x3f]; - output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[7] = '\0'; - - if (size >= 6 && output_size >= 3 + 4 + 4 + 1) { - value = (unsigned long)(unsigned char)input[3] | - ((unsigned long)(unsigned char)input[4] << 8) | - ((unsigned long)(unsigned char)input[5] << 16); - output[7] = _crypt_itoa64[value & 0x3f]; - output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; - output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; - output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; - output[11] = '\0'; - } - - return output; -} +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + * + * This file contains salt generation functions for the traditional and + * other common crypt(3) algorithms, except for bcrypt which is defined + * entirely in crypt_blowfish.c. + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#ifdef _WIN32 +#include "../include/bcrypt/crypt_gensalt.h" +#else +#include "crypt_gensalt.h" +#endif + +unsigned char _crypt_itoa64[64 + 1] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + (void) prefix; + + if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f]; + output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f]; + output[2] = '\0'; + + return output; +} + +char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + +/* Even iteration counts make it easier to detect weak DES keys from a look + * at the hash, so they should be avoided */ + if (size < 3 || output_size < 1 + 4 + 4 + 1 || + (count && (count > 0xffffff || !(count & 1)))) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 725; + + output[0] = '_'; + output[1] = _crypt_itoa64[count & 0x3f]; + output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; + output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; + output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[5] = _crypt_itoa64[value & 0x3f]; + output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[9] = '\0'; + + return output; +} + +char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + + if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = '$'; + output[1] = '1'; + output[2] = '$'; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[3] = _crypt_itoa64[value & 0x3f]; + output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[7] = '\0'; + + if (size >= 6 && output_size >= 3 + 4 + 4 + 1) { + value = (unsigned long)(unsigned char)input[3] | + ((unsigned long)(unsigned char)input[4] << 8) | + ((unsigned long)(unsigned char)input[5] << 16); + output[7] = _crypt_itoa64[value & 0x3f]; + output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[11] = '\0'; + } + + return output; +} diff --git a/src/libbcrypt/src/main.cpp b/dep/libbcrypt/src/main.cpp similarity index 100% rename from src/libbcrypt/src/main.cpp rename to dep/libbcrypt/src/main.cpp diff --git a/src/libbcrypt/src/wrapper.c b/dep/libbcrypt/src/wrapper.c similarity index 96% rename from src/libbcrypt/src/wrapper.c rename to dep/libbcrypt/src/wrapper.c index 98ffeecfd..995e07ca5 100644 --- a/src/libbcrypt/src/wrapper.c +++ b/dep/libbcrypt/src/wrapper.c @@ -1,563 +1,563 @@ -/* - * Written by Solar Designer in 2000-2014. - * No copyright is claimed, and the software is hereby placed in the public - * domain. In case this attempt to disclaim copyright and place the software - * in the public domain is deemed null and void, then the software is - * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the - * general public under the following terms: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted. - * - * There's ABSOLUTELY NO WARRANTY, express or implied. - * - * See crypt_blowfish.c for more information. - */ - -#include -#include - -#include -#ifndef __set_errno -#define __set_errno(val) errno = (val) -#endif - -#ifdef TEST -#include -#include -#include -#include -#include -#include -#ifdef TEST_THREADS -#include -#endif -#endif - -#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1) -#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1) - -#if defined(__GLIBC__) && defined(_LIBC) -#define __SKIP_GNU -#endif - -#ifdef _WIN32 | _WIN64 -#include "../include/bcrypt/ow-crypt.h" - -#include "../include/bcrypt/crypt_blowfish.h" -#include "../include/bcrypt/crypt_gensalt.h" -#else -#include "ow-crypt.h" - -#include "crypt_blowfish.h" -#include "crypt_gensalt.h" -#endif - -#if defined(__GLIBC__) && defined(_LIBC) -/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */ -#include "crypt.h" -extern char *__md5_crypt_r(const char *key, const char *salt, - char *buffer, int buflen); -/* crypt-entry.c needs to be patched to define __des_crypt_r rather than - * __crypt_r, and not define crypt_r and crypt at all */ -extern char *__des_crypt_r(const char *key, const char *salt, - struct crypt_data *data); -extern struct crypt_data _ufc_foobar; -#endif - -static int _crypt_data_alloc(void **data, int *size, int need) -{ - void *updated; - - if (*data && *size >= need) return 0; - - updated = realloc(*data, need); - - if (!updated) { -#ifndef __GLIBC__ - /* realloc(3) on glibc sets errno, so we don't need to bother */ - __set_errno(ENOMEM); -#endif - return -1; - } - -#if defined(__GLIBC__) && defined(_LIBC) - if (need >= sizeof(struct crypt_data)) - ((struct crypt_data *)updated)->initialized = 0; -#endif - - *data = updated; - *size = need; - - return 0; -} - -static char *_crypt_retval_magic(char *retval, const char *setting, - char *output, int size) -{ - if (retval) - return retval; - - if (_crypt_output_magic(setting, output, size)) - return NULL; /* shouldn't happen */ - - return output; -} - -#if defined(__GLIBC__) && defined(_LIBC) -/* - * Applications may re-use the same instance of struct crypt_data without - * resetting the initialized field in order to let crypt_r() skip some of - * its initialization code. Thus, it is important that our multiple hashing - * algorithms either don't conflict with each other in their use of the - * data area or reset the initialized field themselves whenever required. - * Currently, the hashing algorithms simply have no conflicts: the first - * field of struct crypt_data is the 128-byte large DES key schedule which - * __des_crypt_r() calculates each time it is called while the two other - * hashing algorithms use less than 128 bytes of the data area. - */ - -char *__crypt_rn(__const char *key, __const char *setting, - void *data, int size) -{ - if (setting[0] == '$' && setting[1] == '2') - return _crypt_blowfish_rn(key, setting, (char *)data, size); - if (setting[0] == '$' && setting[1] == '1') - return __md5_crypt_r(key, setting, (char *)data, size); - if (setting[0] == '$' || setting[0] == '_') { - __set_errno(EINVAL); - return NULL; - } - if (size >= sizeof(struct crypt_data)) - return __des_crypt_r(key, setting, (struct crypt_data *)data); - __set_errno(ERANGE); - return NULL; -} - -char *__crypt_ra(__const char *key, __const char *setting, - void **data, int *size) -{ - if (setting[0] == '$' && setting[1] == '2') { - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return _crypt_blowfish_rn(key, setting, (char *)*data, *size); - } - if (setting[0] == '$' && setting[1] == '1') { - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return __md5_crypt_r(key, setting, (char *)*data, *size); - } - if (setting[0] == '$' || setting[0] == '_') { - __set_errno(EINVAL); - return NULL; - } - if (_crypt_data_alloc(data, size, sizeof(struct crypt_data))) - return NULL; - return __des_crypt_r(key, setting, (struct crypt_data *)*data); -} - -char *__crypt_r(__const char *key, __const char *setting, - struct crypt_data *data) -{ - return _crypt_retval_magic( - __crypt_rn(key, setting, data, sizeof(*data)), - setting, (char *)data, sizeof(*data)); -} - -char *__crypt(__const char *key, __const char *setting) -{ - return _crypt_retval_magic( - __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)), - setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar)); -} -#else -char *crypt_rn(const char *key, const char *setting, void *data, int size) -{ - return _crypt_blowfish_rn(key, setting, (char *)data, size); -} - -char *crypt_ra(const char *key, const char *setting, - void **data, int *size) -{ - if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) - return NULL; - return _crypt_blowfish_rn(key, setting, (char *)*data, *size); -} - -char *crypt_r(const char *key, const char *setting, void *data) -{ - return _crypt_retval_magic( - crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE), - setting, (char *)data, CRYPT_OUTPUT_SIZE); -} - -char *crypt(const char *key, const char *setting) -{ - static char output[CRYPT_OUTPUT_SIZE]; - - return _crypt_retval_magic( - crypt_rn(key, setting, output, sizeof(output)), - setting, output, sizeof(output)); -} - -#define __crypt_gensalt_rn crypt_gensalt_rn -#define __crypt_gensalt_ra crypt_gensalt_ra -#define __crypt_gensalt crypt_gensalt -#endif - -char *__crypt_gensalt_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size) -{ - char *(*use)(const char *_prefix, unsigned long _count, - const char *_input, int _size, - char *_output, int _output_size); - - /* This may be supported on some platforms in the future */ - if (!input) { - __set_errno(EINVAL); - return NULL; - } - - if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) || - !strncmp(prefix, "$2y$", 4)) - use = _crypt_gensalt_blowfish_rn; - else - if (!strncmp(prefix, "$1$", 3)) - use = _crypt_gensalt_md5_rn; - else - if (prefix[0] == '_') - use = _crypt_gensalt_extended_rn; - else - if (!prefix[0] || - (prefix[0] && prefix[1] && - memchr(_crypt_itoa64, prefix[0], 64) && - memchr(_crypt_itoa64, prefix[1], 64))) - use = _crypt_gensalt_traditional_rn; - else { - __set_errno(EINVAL); - return NULL; - } - - return use(prefix, count, input, size, output, output_size); -} - -char *__crypt_gensalt_ra(const char *prefix, unsigned long count, - const char *input, int size) -{ - char output[CRYPT_GENSALT_OUTPUT_SIZE]; - char *retval; - - retval = __crypt_gensalt_rn(prefix, count, - input, size, output, sizeof(output)); - - if (retval) { -#ifdef _WIN32 | _WIN64 - retval = _strdup(retval); -#else - retval = strdup(retval); -#endif -#ifndef __GLIBC__ - /* strdup(3) on glibc sets errno, so we don't need to bother */ - if (!retval) - __set_errno(ENOMEM); -#endif - } - - return retval; -} - -char *__crypt_gensalt(const char *prefix, unsigned long count, - const char *input, int size) -{ - static char output[CRYPT_GENSALT_OUTPUT_SIZE]; - - return __crypt_gensalt_rn(prefix, count, - input, size, output, sizeof(output)); -} - -#if defined(__GLIBC__) && defined(_LIBC) -weak_alias(__crypt_rn, crypt_rn) -weak_alias(__crypt_ra, crypt_ra) -weak_alias(__crypt_r, crypt_r) -weak_alias(__crypt, crypt) -weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn) -weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra) -weak_alias(__crypt_gensalt, crypt_gensalt) -weak_alias(crypt, fcrypt) -#endif - -#ifdef TEST -static const char *tests[][3] = { - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", - "U*U"}, - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK", - "U*U*"}, - {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a", - "U*U*U"}, - {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui", - "0123456789abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - "chars after 72 are ignored"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xa3"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.", - "\xff\xff\xa3"}, - {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", - "\xff\xff\xa3"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", - "\xa3"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "1\xa3" "345"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "345"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.", - "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", - "\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", - "\xff\xa3" "345"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", - "\xa3" "ab"}, - {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS", - "\xd1\x91"}, - {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS", - "\xd0\xc1\xd2\xcf\xcc\xd8"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6", - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" - "chars after 72 are ignored as usual"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy", - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" - "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"}, - {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe", - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" - "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"}, - {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy", - ""}, - {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."}, - {"*1", "", "*0"}, - {NULL} -}; - -#define which tests[0] - -static volatile sig_atomic_t running; - -static void handle_timer(int signum) -{ - (void) signum; - running = 0; -} - -static void *run(void *arg) -{ - unsigned long count = 0; - int i = 0; - void *data = NULL; - int size = 0x12345678; - - do { - const char *hash = tests[i][0]; - const char *key = tests[i][1]; - const char *setting = tests[i][2]; - - if (!tests[++i][0]) - i = 0; - - if (setting && strlen(hash) < 30) /* not for benchmark */ - continue; - - if (strcmp(crypt_ra(key, hash, &data, &size), hash)) { - printf("%d: FAILED (crypt_ra/%d/%lu)\n", - (int)((char *)arg - (char *)0), i, count); - free(data); - return NULL; - } - count++; - } while (running); - - free(data); - return count + (char *)0; -} - -int main(void) -{ - struct itimerval it; - struct tms buf; - clock_t clk_tck, start_real, start_virtual, end_real, end_virtual; - unsigned long count; - void *data; - int size; - char *setting1, *setting2; - int i; -#ifdef TEST_THREADS - pthread_t t[TEST_THREADS]; - void *t_retval; -#endif - - data = NULL; - size = 0x12345678; - - for (i = 0; tests[i][0]; i++) { - const char *hash = tests[i][0]; - const char *key = tests[i][1]; - const char *setting = tests[i][2]; - const char *p; - int ok = !setting || strlen(hash) >= 30; - int o_size; - char s_buf[30], o_buf[61]; - if (!setting) { - memcpy(s_buf, hash, sizeof(s_buf) - 1); - s_buf[sizeof(s_buf) - 1] = 0; - setting = s_buf; - } - - __set_errno(0); - p = crypt(key, setting); - if ((!ok && !errno) || strcmp(p, hash)) { - printf("FAILED (crypt/%d)\n", i); - return 1; - } - - if (ok && strcmp(crypt(key, hash), hash)) { - printf("FAILED (crypt/%d)\n", i); - return 1; - } - - for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) { - int ok_n = ok && o_size == (int)sizeof(o_buf); - const char *x = "abc"; - strcpy(o_buf, x); - if (o_size >= 3) { - x = "*0"; - if (setting[0] == '*' && setting[1] == '0') - x = "*1"; - } - __set_errno(0); - p = crypt_rn(key, setting, o_buf, o_size); - if ((ok_n && (!p || strcmp(p, hash))) || - (!ok_n && (!errno || p || strcmp(o_buf, x)))) { - printf("FAILED (crypt_rn/%d)\n", i); - return 1; - } - } - - __set_errno(0); - p = crypt_ra(key, setting, &data, &size); - if ((ok && (!p || strcmp(p, hash))) || - (!ok && (!errno || p || strcmp((char *)data, hash)))) { - printf("FAILED (crypt_ra/%d)\n", i); - return 1; - } - } - - setting1 = crypt_gensalt(which[0], 12, data, size); - if (!setting1 || strncmp(setting1, "$2a$12$", 7)) { - puts("FAILED (crypt_gensalt)\n"); - return 1; - } - - setting2 = crypt_gensalt_ra(setting1, 12, data, size); - if (strcmp(setting1, setting2)) { - puts("FAILED (crypt_gensalt_ra/1)\n"); - return 1; - } - - (*(char *)data)++; - setting1 = crypt_gensalt_ra(setting2, 12, data, size); - if (!strcmp(setting1, setting2)) { - puts("FAILED (crypt_gensalt_ra/2)\n"); - return 1; - } - - free(setting1); - free(setting2); - free(data); - -#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) - clk_tck = sysconf(_SC_CLK_TCK); -#else - clk_tck = CLK_TCK; -#endif - - running = 1; - signal(SIGALRM, handle_timer); - - memset(&it, 0, sizeof(it)); - it.it_value.tv_sec = 5; - setitimer(ITIMER_REAL, &it, NULL); - - start_real = times(&buf); - start_virtual = buf.tms_utime + buf.tms_stime; - - count = (char *)run((char *)0) - (char *)0; - - end_real = times(&buf); - end_virtual = buf.tms_utime + buf.tms_stime; - if (end_virtual == start_virtual) end_virtual++; - - printf("%.1f c/s real, %.1f c/s virtual\n", - (float)count * clk_tck / (end_real - start_real), - (float)count * clk_tck / (end_virtual - start_virtual)); - -#ifdef TEST_THREADS - running = 1; - it.it_value.tv_sec = 60; - setitimer(ITIMER_REAL, &it, NULL); - start_real = times(&buf); - - for (i = 0; i < TEST_THREADS; i++) - if (pthread_create(&t[i], NULL, run, i + (char *)0)) { - perror("pthread_create"); - return 1; - } - - for (i = 0; i < TEST_THREADS; i++) { - if (pthread_join(t[i], &t_retval)) { - perror("pthread_join"); - continue; - } - if (!t_retval) continue; - count = (char *)t_retval - (char *)0; - end_real = times(&buf); - printf("%d: %.1f c/s real\n", i, - (float)count * clk_tck / (end_real - start_real)); - } -#endif - - return 0; -} -#endif +/* + * Written by Solar Designer in 2000-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#include +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +#ifdef TEST +#include +#include +#include +#include +#include +#include +#ifdef TEST_THREADS +#include +#endif +#endif + +#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1) +#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1) + +#if defined(__GLIBC__) && defined(_LIBC) +#define __SKIP_GNU +#endif + +#ifdef _WIN32 | _WIN64 +#include "../include/bcrypt/ow-crypt.h" + +#include "../include/bcrypt/crypt_blowfish.h" +#include "../include/bcrypt/crypt_gensalt.h" +#else +#include "ow-crypt.h" + +#include "crypt_blowfish.h" +#include "crypt_gensalt.h" +#endif + +#if defined(__GLIBC__) && defined(_LIBC) +/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */ +#include "crypt.h" +extern char *__md5_crypt_r(const char *key, const char *salt, + char *buffer, int buflen); +/* crypt-entry.c needs to be patched to define __des_crypt_r rather than + * __crypt_r, and not define crypt_r and crypt at all */ +extern char *__des_crypt_r(const char *key, const char *salt, + struct crypt_data *data); +extern struct crypt_data _ufc_foobar; +#endif + +static int _crypt_data_alloc(void **data, int *size, int need) +{ + void *updated; + + if (*data && *size >= need) return 0; + + updated = realloc(*data, need); + + if (!updated) { +#ifndef __GLIBC__ + /* realloc(3) on glibc sets errno, so we don't need to bother */ + __set_errno(ENOMEM); +#endif + return -1; + } + +#if defined(__GLIBC__) && defined(_LIBC) + if (need >= sizeof(struct crypt_data)) + ((struct crypt_data *)updated)->initialized = 0; +#endif + + *data = updated; + *size = need; + + return 0; +} + +static char *_crypt_retval_magic(char *retval, const char *setting, + char *output, int size) +{ + if (retval) + return retval; + + if (_crypt_output_magic(setting, output, size)) + return NULL; /* shouldn't happen */ + + return output; +} + +#if defined(__GLIBC__) && defined(_LIBC) +/* + * Applications may re-use the same instance of struct crypt_data without + * resetting the initialized field in order to let crypt_r() skip some of + * its initialization code. Thus, it is important that our multiple hashing + * algorithms either don't conflict with each other in their use of the + * data area or reset the initialized field themselves whenever required. + * Currently, the hashing algorithms simply have no conflicts: the first + * field of struct crypt_data is the 128-byte large DES key schedule which + * __des_crypt_r() calculates each time it is called while the two other + * hashing algorithms use less than 128 bytes of the data area. + */ + +char *__crypt_rn(__const char *key, __const char *setting, + void *data, int size) +{ + if (setting[0] == '$' && setting[1] == '2') + return _crypt_blowfish_rn(key, setting, (char *)data, size); + if (setting[0] == '$' && setting[1] == '1') + return __md5_crypt_r(key, setting, (char *)data, size); + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (size >= sizeof(struct crypt_data)) + return __des_crypt_r(key, setting, (struct crypt_data *)data); + __set_errno(ERANGE); + return NULL; +} + +char *__crypt_ra(__const char *key, __const char *setting, + void **data, int *size) +{ + if (setting[0] == '$' && setting[1] == '2') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' && setting[1] == '1') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return __md5_crypt_r(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (_crypt_data_alloc(data, size, sizeof(struct crypt_data))) + return NULL; + return __des_crypt_r(key, setting, (struct crypt_data *)*data); +} + +char *__crypt_r(__const char *key, __const char *setting, + struct crypt_data *data) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, data, sizeof(*data)), + setting, (char *)data, sizeof(*data)); +} + +char *__crypt(__const char *key, __const char *setting) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)), + setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar)); +} +#else +char *crypt_rn(const char *key, const char *setting, void *data, int size) +{ + return _crypt_blowfish_rn(key, setting, (char *)data, size); +} + +char *crypt_ra(const char *key, const char *setting, + void **data, int *size) +{ + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); +} + +char *crypt_r(const char *key, const char *setting, void *data) +{ + return _crypt_retval_magic( + crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE), + setting, (char *)data, CRYPT_OUTPUT_SIZE); +} + +char *crypt(const char *key, const char *setting) +{ + static char output[CRYPT_OUTPUT_SIZE]; + + return _crypt_retval_magic( + crypt_rn(key, setting, output, sizeof(output)), + setting, output, sizeof(output)); +} + +#define __crypt_gensalt_rn crypt_gensalt_rn +#define __crypt_gensalt_ra crypt_gensalt_ra +#define __crypt_gensalt crypt_gensalt +#endif + +char *__crypt_gensalt_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + char *(*use)(const char *_prefix, unsigned long _count, + const char *_input, int _size, + char *_output, int _output_size); + + /* This may be supported on some platforms in the future */ + if (!input) { + __set_errno(EINVAL); + return NULL; + } + + if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) || + !strncmp(prefix, "$2y$", 4)) + use = _crypt_gensalt_blowfish_rn; + else + if (!strncmp(prefix, "$1$", 3)) + use = _crypt_gensalt_md5_rn; + else + if (prefix[0] == '_') + use = _crypt_gensalt_extended_rn; + else + if (!prefix[0] || + (prefix[0] && prefix[1] && + memchr(_crypt_itoa64, prefix[0], 64) && + memchr(_crypt_itoa64, prefix[1], 64))) + use = _crypt_gensalt_traditional_rn; + else { + __set_errno(EINVAL); + return NULL; + } + + return use(prefix, count, input, size, output, output_size); +} + +char *__crypt_gensalt_ra(const char *prefix, unsigned long count, + const char *input, int size) +{ + char output[CRYPT_GENSALT_OUTPUT_SIZE]; + char *retval; + + retval = __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); + + if (retval) { +#ifdef _WIN32 | _WIN64 + retval = _strdup(retval); +#else + retval = strdup(retval); +#endif +#ifndef __GLIBC__ + /* strdup(3) on glibc sets errno, so we don't need to bother */ + if (!retval) + __set_errno(ENOMEM); +#endif + } + + return retval; +} + +char *__crypt_gensalt(const char *prefix, unsigned long count, + const char *input, int size) +{ + static char output[CRYPT_GENSALT_OUTPUT_SIZE]; + + return __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); +} + +#if defined(__GLIBC__) && defined(_LIBC) +weak_alias(__crypt_rn, crypt_rn) +weak_alias(__crypt_ra, crypt_ra) +weak_alias(__crypt_r, crypt_r) +weak_alias(__crypt, crypt) +weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn) +weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra) +weak_alias(__crypt_gensalt, crypt_gensalt) +weak_alias(crypt, fcrypt) +#endif + +#ifdef TEST +static const char *tests[][3] = { + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", + "U*U"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK", + "U*U*"}, + {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a", + "U*U*U"}, + {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui", + "0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "chars after 72 are ignored"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.", + "\xff\xff\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "1\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS", + "\xd1\x91"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS", + "\xd0\xc1\xd2\xcf\xcc\xd8"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "chars after 72 are ignored as usual"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy", + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe", + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy", + ""}, + {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*1", "", "*0"}, + {NULL} +}; + +#define which tests[0] + +static volatile sig_atomic_t running; + +static void handle_timer(int signum) +{ + (void) signum; + running = 0; +} + +static void *run(void *arg) +{ + unsigned long count = 0; + int i = 0; + void *data = NULL; + int size = 0x12345678; + + do { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + + if (!tests[++i][0]) + i = 0; + + if (setting && strlen(hash) < 30) /* not for benchmark */ + continue; + + if (strcmp(crypt_ra(key, hash, &data, &size), hash)) { + printf("%d: FAILED (crypt_ra/%d/%lu)\n", + (int)((char *)arg - (char *)0), i, count); + free(data); + return NULL; + } + count++; + } while (running); + + free(data); + return count + (char *)0; +} + +int main(void) +{ + struct itimerval it; + struct tms buf; + clock_t clk_tck, start_real, start_virtual, end_real, end_virtual; + unsigned long count; + void *data; + int size; + char *setting1, *setting2; + int i; +#ifdef TEST_THREADS + pthread_t t[TEST_THREADS]; + void *t_retval; +#endif + + data = NULL; + size = 0x12345678; + + for (i = 0; tests[i][0]; i++) { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + const char *p; + int ok = !setting || strlen(hash) >= 30; + int o_size; + char s_buf[30], o_buf[61]; + if (!setting) { + memcpy(s_buf, hash, sizeof(s_buf) - 1); + s_buf[sizeof(s_buf) - 1] = 0; + setting = s_buf; + } + + __set_errno(0); + p = crypt(key, setting); + if ((!ok && !errno) || strcmp(p, hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + if (ok && strcmp(crypt(key, hash), hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) { + int ok_n = ok && o_size == (int)sizeof(o_buf); + const char *x = "abc"; + strcpy(o_buf, x); + if (o_size >= 3) { + x = "*0"; + if (setting[0] == '*' && setting[1] == '0') + x = "*1"; + } + __set_errno(0); + p = crypt_rn(key, setting, o_buf, o_size); + if ((ok_n && (!p || strcmp(p, hash))) || + (!ok_n && (!errno || p || strcmp(o_buf, x)))) { + printf("FAILED (crypt_rn/%d)\n", i); + return 1; + } + } + + __set_errno(0); + p = crypt_ra(key, setting, &data, &size); + if ((ok && (!p || strcmp(p, hash))) || + (!ok && (!errno || p || strcmp((char *)data, hash)))) { + printf("FAILED (crypt_ra/%d)\n", i); + return 1; + } + } + + setting1 = crypt_gensalt(which[0], 12, data, size); + if (!setting1 || strncmp(setting1, "$2a$12$", 7)) { + puts("FAILED (crypt_gensalt)\n"); + return 1; + } + + setting2 = crypt_gensalt_ra(setting1, 12, data, size); + if (strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/1)\n"); + return 1; + } + + (*(char *)data)++; + setting1 = crypt_gensalt_ra(setting2, 12, data, size); + if (!strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/2)\n"); + return 1; + } + + free(setting1); + free(setting2); + free(data); + +#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) + clk_tck = sysconf(_SC_CLK_TCK); +#else + clk_tck = CLK_TCK; +#endif + + running = 1; + signal(SIGALRM, handle_timer); + + memset(&it, 0, sizeof(it)); + it.it_value.tv_sec = 5; + setitimer(ITIMER_REAL, &it, NULL); + + start_real = times(&buf); + start_virtual = buf.tms_utime + buf.tms_stime; + + count = (char *)run((char *)0) - (char *)0; + + end_real = times(&buf); + end_virtual = buf.tms_utime + buf.tms_stime; + if (end_virtual == start_virtual) end_virtual++; + + printf("%.1f c/s real, %.1f c/s virtual\n", + (float)count * clk_tck / (end_real - start_real), + (float)count * clk_tck / (end_virtual - start_virtual)); + +#ifdef TEST_THREADS + running = 1; + it.it_value.tv_sec = 60; + setitimer(ITIMER_REAL, &it, NULL); + start_real = times(&buf); + + for (i = 0; i < TEST_THREADS; i++) + if (pthread_create(&t[i], NULL, run, i + (char *)0)) { + perror("pthread_create"); + return 1; + } + + for (i = 0; i < TEST_THREADS; i++) { + if (pthread_join(t[i], &t_retval)) { + perror("pthread_join"); + continue; + } + if (!t_retval) continue; + count = (char *)t_retval - (char *)0; + end_real = times(&buf); + printf("%d: %.1f c/s real\n", i, + (float)count * clk_tck / (end_real - start_real)); + } +#endif + + return 0; +} +#endif diff --git a/src/libbcrypt/src/x86.S b/dep/libbcrypt/src/x86.S similarity index 100% rename from src/libbcrypt/src/x86.S rename to dep/libbcrypt/src/x86.S diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln similarity index 98% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln index 38c2d4047..70256fc2d 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.sln +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.sln @@ -1,41 +1,41 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.136 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbcrypt", "libbcrypt.vcxproj", "{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\test\test.vcxproj", "{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.ActiveCfg = Debug|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.Build.0 = Debug|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.ActiveCfg = Debug|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.Build.0 = Debug|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.ActiveCfg = Release|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.Build.0 = Release|x64 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.ActiveCfg = Release|Win32 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.Build.0 = Release|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.ActiveCfg = Debug|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.Build.0 = Debug|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.ActiveCfg = Debug|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.Build.0 = Debug|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.ActiveCfg = Release|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.Build.0 = Release|x64 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.ActiveCfg = Release|Win32 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2DB786F9-9679-4A72-A4A0-2544E42B78CB} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.136 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbcrypt", "libbcrypt.vcxproj", "{D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "..\test\test.vcxproj", "{C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.ActiveCfg = Debug|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x64.Build.0 = Debug|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.ActiveCfg = Debug|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Debug|x86.Build.0 = Debug|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.ActiveCfg = Release|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x64.Build.0 = Release|x64 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.ActiveCfg = Release|Win32 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4}.Release|x86.Build.0 = Release|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.ActiveCfg = Debug|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x64.Build.0 = Debug|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.ActiveCfg = Debug|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Debug|x86.Build.0 = Debug|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.ActiveCfg = Release|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x64.Build.0 = Release|x64 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.ActiveCfg = Release|Win32 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2DB786F9-9679-4A72-A4A0-2544E42B78CB} + EndGlobalSection +EndGlobal diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj similarity index 97% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj index 577af5635..e97c78180 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj @@ -1,135 +1,135 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4} - libbcrypt - 10.0.17763.0 - - - - StaticLibrary - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - - - - - Level3 - Disabled - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D6A9A3F3-1312-4494-85B8-7CE7DD4D78F4} + libbcrypt + 10.0.17763.0 + + + + StaticLibrary + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters similarity index 97% rename from src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters rename to dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters index e30024695..8edc396c8 100644 --- a/src/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters +++ b/dep/libbcrypt/vs2017/libbcrypt/libbcrypt.vcxproj.filters @@ -1,54 +1,54 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/test/main.cpp b/dep/libbcrypt/vs2017/test/main.cpp similarity index 96% rename from src/libbcrypt/vs2017/test/main.cpp rename to dep/libbcrypt/vs2017/test/main.cpp index fac9b7134..fa8994b20 100644 --- a/src/libbcrypt/vs2017/test/main.cpp +++ b/dep/libbcrypt/vs2017/test/main.cpp @@ -1,22 +1,22 @@ -#include "../../include/bcrypt/BCrypt.hpp" -#include - -using namespace std; - -int main() { - string right_password = "right_password"; - string wrong_password = "wrong_password"; - - cout << "generate hash... " << flush; - string hash = BCrypt::generateHash(right_password, 12); - cout << "done." << endl; - - cout << "checking right password: " << flush - << BCrypt::validatePassword(right_password, hash) << endl; - - cout << "checking wrong password: " << flush - << BCrypt::validatePassword(wrong_password, hash) << endl; - - system("pause"); - return 0; -} +#include "../../include/bcrypt/BCrypt.hpp" +#include + +using namespace std; + +int main() { + string right_password = "right_password"; + string wrong_password = "wrong_password"; + + cout << "generate hash... " << flush; + string hash = BCrypt::generateHash(right_password, 12); + cout << "done." << endl; + + cout << "checking right password: " << flush + << BCrypt::validatePassword(right_password, hash) << endl; + + cout << "checking wrong password: " << flush + << BCrypt::validatePassword(wrong_password, hash) << endl; + + system("pause"); + return 0; +} diff --git a/src/libbcrypt/vs2017/test/test.vcxproj b/dep/libbcrypt/vs2017/test/test.vcxproj similarity index 97% rename from src/libbcrypt/vs2017/test/test.vcxproj rename to dep/libbcrypt/vs2017/test/test.vcxproj index a05b3cf6e..566addc6b 100644 --- a/src/libbcrypt/vs2017/test/test.vcxproj +++ b/dep/libbcrypt/vs2017/test/test.vcxproj @@ -1,131 +1,131 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - {d6a9a3f3-1312-4494-85b8-7ce7dd4d78f4} - - - - 15.0 - {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68} - test - 10.0.17763.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - - - ../libbcrypt/Debug/libbcrypt.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - - - true - true - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + {d6a9a3f3-1312-4494-85b8-7ce7dd4d78f4} + + + + 15.0 + {C6B1CF6E-88DF-4109-9EF4-06CBA5BD5B68} + test + 10.0.17763.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + ../libbcrypt/Debug/libbcrypt.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + \ No newline at end of file diff --git a/src/libbcrypt/vs2017/test/test.vcxproj.filters b/dep/libbcrypt/vs2017/test/test.vcxproj.filters similarity index 96% rename from src/libbcrypt/vs2017/test/test.vcxproj.filters rename to dep/libbcrypt/vs2017/test/test.vcxproj.filters index 128a38664..a91ad0ed9 100644 --- a/src/libbcrypt/vs2017/test/test.vcxproj.filters +++ b/dep/libbcrypt/vs2017/test/test.vcxproj.filters @@ -1,22 +1,22 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + \ No newline at end of file diff --git a/dep/span-lite/CMakeLists.txt b/dep/span-lite/CMakeLists.txt new file mode 100644 index 000000000..4d4f9d43f --- /dev/null +++ b/dep/span-lite/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(span-lite INTERFACE) +add_library(martinmoene::span-lite ALIAS span-lite) + +target_include_directories(span-lite INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/dep/span-lite/LICENSE.txt b/dep/span-lite/LICENSE.txt new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/dep/span-lite/LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dep/span-lite/README.md b/dep/span-lite/README.md new file mode 100644 index 000000000..4b077c889 --- /dev/null +++ b/dep/span-lite/README.md @@ -0,0 +1,514 @@ + +# span lite: A single-file header-only version of a C++20-like span for C++98, C++11 and later + +[![Language](https://img.shields.io/badge/C%2B%2B-98/11/14/17/20-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-BSL-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![Build Status](https://travis-ci.org/martinmoene/span-lite.svg?branch=master)](https://travis-ci.org/martinmoene/span-lite) [![Build status](https://ci.appveyor.com/api/projects/status/1ha3wnxtam547m8p?svg=true)](https://ci.appveyor.com/project/martinmoene/span-lite) [![Version](https://badge.fury.io/gh/martinmoene%2Fspan-lite.svg)](https://github.com/martinmoene/span-lite/releases) [![download](https://img.shields.io/badge/latest-download-blue.svg)](https://github.com/martinmoene/span-lite/blob/master/include/nonstd/span.hpp) [![Conan](https://img.shields.io/badge/on-conan-blue.svg)](https://conan.io/center/span-lite) [![Try it on wandbox](https://img.shields.io/badge/on-wandbox-blue.svg)](https://wandbox.org/permlink/venR3Ko2Q4tlvcVk) [![Try it on godbolt online](https://img.shields.io/badge/on-godbolt-blue.svg)](https://godbolt.org/z/htwpnb) + +**Contents** + +- [Example usage](#example-usage) +- [In a nutshell](#in-a-nutshell) +- [License](#license) +- [Dependencies](#dependencies) +- [Installation and use](#installation-and-use) +- [Synopsis](#synopsis) +- [Reported to work with](#reported-to-work-with) +- [Building the tests](#building-the-tests) +- [Other implementations of span](#other-implementations-of-span) +- [Notes and references](#notes-and-references) +- [Appendix](#appendix) + +## Example usage + +```cpp +#include "nonstd/span.hpp" +#include +#include +#include + +std::ptrdiff_t size( nonstd::span spn ) +{ + return spn.size(); +} + +int main() +{ + int arr[] = { 1, }; + + std::cout << + "C-array:" << size( arr ) << + " array:" << size( std::array { 1, 2, } ) << + " vector:" << size( std::vector{ 1, 2, 3, } ); +} +``` + +### Compile and run + +```bash +prompt> g++ -std=c++11 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe +C-array:1 array:2 vector:3 +``` + +## In a nutshell + +**span lite** is a single-file header-only library to provide a bounds-safe view for sequences of objects. The library provides a [C++20-like span](http://en.cppreference.com/w/cpp/container/span) for use with C++98 and later. If available, `std::span` is used, unless [configured otherwise](#configuration). *span-lite* can detect the presence of [*byte-lite*](https://github.com/martinmoene/byte-lite) and if present, it provides `as_bytes()` and `as_writable_bytes()` also for C++14 and earlier. + +**Features and properties of span lite** are ease of installation (single header), freedom of dependencies other than the standard library. To compensate for the class template argument deduction that is missing from pre-C++17 compilers, `nonstd::span` can provide `make_span` functions. See [configuration](#configuration). + +## License + +*span lite* is distributed under the [Boost Software License](https://github.com/martinmoene/span-lite/blob/master/LICENSE.txt). + +## Dependencies + +*span lite* has no other dependencies than the [C++ standard library](http://en.cppreference.com/w/cpp/header). + +## Installation and use + +*span lite* is a single-file header-only library. Put `span.hpp` in the [include](include) folder directly into the project source tree or somewhere reachable from your project. + +## Synopsis + +**Contents** +[Documentation of `std::span`](#documentation-of-stdspan) +[Later additions](#later-additions) +[Non-standard extensions](#non-standard-extensions) +[Configuration](#configuration) + +## Documentation of `std::span` + +Depending on the compiler and C++-standard used, `nonstd::span` behaves less or more like `std::span`. To get an idea of the capabilities of `nonstd::span` with your configuration, look at the output of the [tests](test/span.t.cpp), issuing `span-main.t --pass @`. For `std::span`, see its [documentation at cppreference](http://en.cppreference.com/w/cpp/container/span). + +## Later additions + +### `back()` and `front()` + +*span lite* can provide `back()` and `front()` member functions for element access. See the table below and section [configuration](#configuration). + +## Non-standard extensions + +### Construct from container + +To construct a span from a container with compilers that cannot constrain such a single-parameter constructor to containers, *span lite* provides a constructor that takes an additional parameter of type `with_container_t`. Use `with_container` as value for this parameter. See the table below and section [configuration](#configuration). + +### Construct from `std::array` with const data + +*span lite* can provide construction of a span from a `std::array` with const data. See the table below and section [configuration](#configuration). + +### `operator()` + +*span lite* can provide member function call `operator()` for element access. It is equivalent to `operator[]` and has been marked `[[deprecated]]`. Its main purpose is to provide a migration path. + +### `at()` + +*span lite* can provide member function `at()` for element access. Unless exceptions have been disabled, `at()` throws std::out_of_range if the index falls outside the span. With exceptions disabled, `at(index_t)` delegates bounds checking to `operator[](index_t)`. See the table below and sections [configuration](#configuration) and [disable exceptions](#disable-exceptions). + +### `swap()` + +*span lite* can provide a `swap()`member function. See the table below and section [configuration](#configuration). + +### `operator==()` and other comparison functions + +*span lite* can provide functions to compare the content of two spans. However, C++20's span will not provide comparison and _span lite_ will omit comparison at default in the near future. See the table below and section [configuration](#configuration). See also [Revisiting Regular Types](#regtyp). + +### `same()` + +*span lite* can provide function `same()` to determine if two spans refer as identical spans to the same data via the same type. If `same()` is enabled, `operator==()` incorporates it in its comparison. See the table below and section [configuration](#configuration). + +### `first()`, `last()` and `subspan()` + +*span lite* can provide functions `first()`, `last()` and `subspan()` to avoid having to use the *dot template* syntax when the span is a dependent type. See the table below and section [configuration](#configuration). + +### `make_span()` + +*span lite* can provide `make_span()` creator functions to compensate for the class template argument deduction that is missing from pre-C++17 compilers. See the table below and section [configuration](#configuration). + +### `byte_span()` + +*span lite* can provide `byte_span()` creator functions to represent an object as a span of bytes. This requires the C++17 type `std::byte` to be available. See the table below and section [configuration](#configuration). + +| Kind | std | Function or method | +|--------------------|------|--------------------| +| **Macro** | | macro **`span_FEATURE_WITH_CONTAINER`**
macro **`span_FEATURE_WITH_CONTAINER_TO_STD`** | +| **Types** | | **with_container_t** type to disambiguate below constructors | +| **Objects** | | **with_container** value to disambiguate below constructors | +| **Constructors** | | macro **`span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE`**| +|   | | template<class Container>
constexpr **span**(with_container_t, Container & cont) | +|   | | template<class Container>
constexpr **span**(with_container_t, Container const & cont) | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_CALL_OPERATOR`** | +|   | | constexpr reference **operator()**(index_t idx) const
Equivalent to **operator[]**(), marked `[[deprecated]]` | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_AT`** | +|   | | constexpr reference **at**(index_t idx) const
May throw std::out_of_range exception | +|   | |   | +| **Methods** | | macro **`span_FEATURE_MEMBER_BACK_FRONT`** (on since v0.5.0) | +|   | | constexpr reference **back()** const noexcept | +|   | | constexpr reference **front()** const noexcept | +|   | |   | +| **Method** | | macro **`span_FEATURE_MEMBER_SWAP`** | +|   | | constexpr void **swap**(span & other) noexcept | +|   | |   | +| **Free functions** | | macro **`span_FEATURE_COMPARISON`** | +|

== != < > <= >= | | template<class T1, index_t E1, class T2, index_t E2>
constexpr bool
**operator==**( span const & l, span const & r) noexcept | +|   | |   | +| **Free function** | | macro **`span_FEATURE_SAME`** | +|   | | template<class T1, index_t E1, class T2, index_t E2>
constexpr bool
**same**( span const & l, span const & r) noexcept | +|   | |   | +| **Free functions** | | macro **`span_FEATURE_NON_MEMBER_FIRST_LAST_SUB`** | +|   | >= C++11 | template<extent_t Count, class T>
constexpr auto
**first**(T & t) ->... | +|   | >= C++11 | template<class T>
constexpr auto
**first**(T & t, index_t count) ->... | +|   | >= C++11 | template<extent_t Count, class T>
constexpr auto
**last**(T & t) ->... | +|   | >= C++11 | template<class T>
constexpr auto
**last**(T & t, extent_t count) ->... | +|   | >= C++11 | template<index_t Offset, extent_t Count = dynamic_extent, class T>
constexpr auto
**subspan**(T & t) ->... | +|   | >= C++11 | template<class T>
constexpr auto
**subspan**(T & t, index_t offset, extent_t count = dynamic_extent) ->... | +|   |   |   | +| **Free functions** | | macro **`span_FEATURE_MAKE_SPAN`**
macro **`span_FEATURE_MAKE_SPAN_TO_STD`** | +|   |   | template<class T>
constexpr span<T>
**make_span**(T \* first, T \* last) noexcept | +|   |   | template<class T>
constexpr span<T>
**make_span**(T \* ptr, index_t count) noexcept | +|   |   | template<class T, size_t N>
constexpr span<T,N>
**make_span**(T (&arr)[N]) noexcept | +|   | >= C++11 | template<class T, size_t N>
constexpr span<T,N>
**make_span**(std::array<T,N> & arr) noexcept | +|   | >= C++11 | template<class T, size_t N>
constexpr span<const T,N>
**make_span**(std::array<T,N > const & arr) noexcept | +|   | >= C++11 | template<class Container>
constexpr auto
**make_span**(Container & cont) ->
 span<typename Container::value_type> noexcept | +|   | >= C++11 | template<class Container>
constexpr auto
**make_span**(Container const & cont) ->
 span<const typename Container::value_type> noexcept | +|   |   | template<class Container>
span<typename Container::value_type>
**make_span**( with_container_t, Container & cont ) | +|   |   | template<class Container>
span<const typename Container::value_type>
**make_span**( with_container_t, Container const & cont ) | +|   | < C++11 | template<class T, Allocator>
span<T>
**make_span**(std::vector<T, Allocator> & cont) | +|   | < C++11 | template<class T, Allocator>
span<const T>
**make_span**(std::vector<T, Allocator> const & cont) | +|   |   |   | +| **Free functions** | | macro **`span_FEATURE_BYTE_SPAN`** | +|   | >= C++11 | template<class T>
span<T, sizeof(T)>
**byte_span**(T & t) | +|   | >= C++11 | template<class T>
span<const T, sizeof(T)>
**byte_span**(T const & t) | + +## Configuration + +### Tweak header + +If the compiler supports [`__has_include()`](https://en.cppreference.com/w/cpp/preprocessor/include), *span lite* supports the [tweak header](https://vector-of-bool.github.io/2020/10/04/lib-configuration.html) mechanism. Provide your *tweak header* as `nonstd/span.tweak.hpp` in a folder in the include-search-path. In the tweak header, provide definitions as documented below, like `#define span_CONFIG_NO_EXCEPTIONS 1`. + +### Standard selection macro + +\-Dspan\_CPLUSPLUS=199711L +Define this macro to override the auto-detection of the supported C++ standard, if your compiler does not set the `__cplusplus` macro correctly. + +### Select `std::span` or `nonstd::span` + +At default, *span lite* uses `std::span` if it is available and lets you use it via namespace `nonstd`. You can however override this default and explicitly request to use `std::span` or span lite's `nonstd::span` as `nonstd::span` via the following macros. + +-Dspan\_CONFIG\_SELECT\_SPAN=span_SPAN_DEFAULT +Define this to `span_SPAN_STD` to select `std::span` as `nonstd::span`. Define this to `span_SPAN_NONSTD` to select `nonstd::span` as `nonstd::span`. Default is undefined, which has the same effect as defining to `span_SPAN_DEFAULT`. + +### Select extent type + +-Dspan_CONFIG_EXTENT_TYPE=std::size_t +Define this to `std::ptrdiff_t` to use the signed type. The default is `std::size_t`, as in C++20 (since v0.7.0). + +### Select size type + +-Dspan_CONFIG_SIZE_TYPE=std::size_t +Define this to `std::ptrdiff_t` to use the signed type. The default is `std::size_t`, as in C++20 (since v0.7.0). Note `span_CONFIG_SIZE_TYPE` replaces `span_CONFIG_INDEX_TYPE` which is deprecated. + +### Disable exceptions + +-Dspan_CONFIG_NO_EXCEPTIONS=0 +Define this to 1 if you want to compile without exceptions. If not defined, the header tries and detect if exceptions have been disabled (e.g. via `-fno-exceptions`). Disabling exceptions will force contract violation to use termination, see [contract violation macros](#contract-violation-response-macros). Default is undefined. + +### Provide construction using `with_container_t` + +-Dspan_FEATURE_WITH_CONTAINER=0 +Define this to 1 to enable constructing a span using `with_container_t`. Note that `span_FEATURE_WITH_CONTAINER` takes precedence over `span_FEATURE_WITH_CONTAINER_TO_STD`. Default is undefined. + +-Dspan_FEATURE_WITH_CONTAINER_TO_STD=*n* +Define this to the highest C++ language version for which to enable constructing a span using `with_container_t`, like 98, 03, 11, 14, 17, 20. You can use 99 for inclusion with any standard, but prefer to use `span_FEATURE_WITH_CONTAINER` for this. Note that `span_FEATURE_WITH_CONTAINER` takes precedence over `span_FEATURE_WITH_CONTAINER_TO_STD`. Default is undefined. + +### Provide construction from `std::array` with const data + +-Dspan_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE=0 +Define this to 1 to enable constructing a span from a std::array with const data. Default is undefined. + +### Provide `operator()` member function + +-Dspan_FEATURE_MEMBER_CALL_OPERATOR=0 +Define this to 1 to provide member function `operator()`for element access. It is equivalent to `operator[]` and has been marked `[[deprecated]]`. Its main purpose is to provide a migration path. Default is undefined. + +### Provide `at()` member function + +-Dspan_FEATURE_MEMBER_AT=0 +Define this to 1 to provide member function `at()`. Define this to 2 to include index and size in message of std::out_of_range exception. Default is undefined. + +### Provide `back()` and `front()` member functions + +-Dspan_FEATURE_MEMBER_BACK_FRONT=1 _(on since v0.5.0)_ +Define this to 0 to omit member functions `back()` and `front()`. Default is undefined. + +### Provide `swap()` member function + +-Dspan_FEATURE_MEMBER_SWAP=0 +Define this to 1 to provide member function `swap()`. Default is undefined. + +### Provide `operator==()` and other comparison functions + +-Dspan_FEATURE_COMPARISON=0 +Define this to 1 to include the comparison functions to compare the content of two spans. C++20's span does not provide comparison and _span lite_ omits comparison from v0.7.0. Default is undefined. + +### Provide `same()` function + +-Dspan_FEATURE_SAME=0 +Define this to 1 to provide function `same()` to test if two spans refer as identical spans to the same data via the same type. If `same()` is enabled, `operator==()` incorporates it in its comparison. Default is undefined. + +### Provide `first()`, `last()` and `subspan()` functions + +-Dspan_FEATURE_NON_MEMBER_FIRST_LAST_SUB=0 +Define this to 1 to provide functions `first()`, `last()` and `subspan()`. This implies `span_FEATURE_MAKE_SPAN` to provide functions `make_span()` that are required for this feature. Default is undefined. + +### Provide `make_span()` functions + +-Dspan_FEATURE_MAKE_SPAN=0 +Define this to 1 to provide creator functions `nonstd::make_span()`. This feature is implied by using `span_FEATURE_NON_MEMBER_FIRST_LAST_SUB=1`. Note that `span_FEATURE_MAKE_SPAN` takes precedence over `span_FEATURE_MAKE_SPAN_TO_STD`. Default is undefined. + +-Dspan_FEATURE_MAKE_SPAN_TO_STD=*n* +Define this to the highest C++ language version for which to provide creator functions `nonstd::make_span()`, like 98, 03, 11, 14, 17, 20. You can use 99 for inclusion with any standard, but prefer to use `span_FEATURE_MAKE_SPAN` for this. Note that `span_FEATURE_MAKE_SPAN` takes precedence over `span_FEATURE_MAKE_SPAN_TO_STD`. Default is undefined. + +### Provide `byte_span()` functions + +-Dspan_FEATURE_BYTE_SPAN=0 +Define this to 1 to provide creator functions `nonstd::byte_span()`. Default is undefined. + +### Contract violation response macros + +*span-lite* provides contract violation response control as suggested in proposal [N4415](http://wg21.link/n4415). + +\-Dspan\_CONFIG\_CONTRACT\_LEVEL\_ON (*default*) +Define this macro to include both `span_EXPECTS` and `span_ENSURES` in the code. This is the default case. + +\-Dspan\_CONFIG\_CONTRACT\_LEVEL\_OFF +Define this macro to exclude both `span_EXPECTS` and `span_ENSURES` from the code. + +\-Dspan\_CONFIG_CONTRACT\_LEVEL\_EXPECTS\_ONLY +Define this macro to include `span_EXPECTS` in the code and exclude `span_ENSURES` from the code. + +\-Dspan\_CONFIG\_CONTRACT\_LEVEL\_ENSURES\_ONLY +Define this macro to exclude `span_EXPECTS` from the code and include `span_ENSURES` in the code. + +\-Dspan\_CONFIG\_CONTRACT\_VIOLATION\_TERMINATES (*default*) +Define this macro to call `std::terminate()` on a contract violation in `span_EXPECTS`, `span_ENSURES`. This is the default case. + +\-Dspan\_CONFIG\_CONTRACT\_VIOLATION\_THROWS +Define this macro to throw an exception of implementation-defined type that is derived from `std::runtime_exception` instead of calling `std::terminate()` on a contract violation in `span_EXPECTS` and `span_ENSURES`. See also [disable exceptions](#disable-exceptions). + +Reported to work with +-------------------- +The table below mentions the compiler versions *span lite* is reported to work with. + +OS | Compiler | Where | Versions | +------------:|:-----------|:--------|:---------| +**GNU/Linux**| Clang/LLVM | Travis | 3.5.0, 3.6.2, 3.7.1, 3.8.0, 3.9.1, 4.0.1 | +   | GCC | Travis | 5.5.0, 6.4.0, 7.3.0 | +**OS X** | ? | Local | ? | +**Windows** | Clang/LLVM | Local | 6.0.0 | +  | GCC | Local | 7.2.0 | +  | Visual C++
(Visual Studio)| Local | 8 (2005), 10 (2010), 11 (2012),
12 (2013), 14 (2015), 15 (2017) | +  | Visual C++
(Visual Studio)| AppVeyor | 10 (2010), 11 (2012),
12 (2013), 14 (2015), 15 (2017) | + +## Building the tests + +To build the tests you need: + +- [CMake](http://cmake.org), version 3.0 or later to be installed and in your PATH. +- A [suitable compiler](#reported-to-work-with). + +The [*lest* test framework](https://github.com/martinmoene/lest) is included in the [test folder](test). + +The following steps assume that the [*span lite* source code](https://github.com/martinmoene/span-lite) has been cloned into a directory named `./span-lite`. + +1. Create a directory for the build outputs. + + cd ./span-lite + md build && cd build + +2. Configure CMake to use the compiler of your choice (run `cmake --help` for a list). + + cmake -G "Unix Makefiles" -DSPAN_LITE_OPT_BUILD_TESTS=ON .. + +3. Optional. You can control above configuration through the following options: + + `-DSPAN_LITE_OPT_BUILD_TESTS=ON`: build the tests for span, default off + `-DSPAN_LITE_OPT_BUILD_EXAMPLES=OFF`: build the examples, default off + +4. Build the test suite. + + cmake --build . + +5. Run the test suite. + + ctest -V + +All tests should pass, indicating your platform is supported and you are ready to use *span lite*. + +## Other implementations of span + +- *gsl-lite* [span](https://github.com/martinmoene/gsl-lite/blob/73c4f16f2b35fc174fc2f09d44d5ab13e5c638c3/include/gsl/gsl-lite.hpp#L1221). +- Microsoft GSL [span](https://github.com/Microsoft/GSL/blob/master/include/gsl/span). +- Google Abseil [span](https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h). +- Marshall Clow's [libc++ span snippet](https://github.com/mclow/snippets/blob/master/span.cpp). +- Tristan Brindle's [Implementation of C++20's std::span for older compilers](https://github.com/tcbrindle/span). +- [Search _span c++_ on GitHub](https://github.com/search?l=C%2B%2B&q=span+c%2B%2B&type=Repositories&utf8=%E2%9C%93). + +## Notes and references + +*Interface and specification* + +- [span on cppreference](https://en.cppreference.com/w/cpp/container/span). +- [p0122 - C++20 Proposal](http://wg21.link/p0122). +- [span in C++20 Working Draft](http://eel.is/c++draft/views). + +*Presentations* + +- TBD + +*Proposals* + +- [p0122 - span: bounds-safe views for sequences of objects](http://wg21.link/p0122). +- [p1024 - Usability Enhancements for std::span](http://wg21.link/p1024). +- [p1419 - A SFINAE-friendly trait to determine the extent of statically sized containers](http://wg21.link/p1419). +- [p0805 - Comparing Containers](http://wg21.link/p0805). +- [p1085 - Should Span be Regular?](http://wg21.link/p0805). +- [p0091 - Template argument deduction for class templates](http://wg21.link/p0091). +- [p0856 - Restrict Access Property for mdspan and span](http://wg21.link/p0856). +- [p1428 - Subscripts and sizes should be signed](http://wg21.link/p1428). +- [p1089 - Sizes Should Only span Unsigned](http://wg21.link/p1089). +- [p1227 - Signed size() functions](http://wg21.link/p1227). +- [p1872 - span should have size_type, not index_type](http://wg21.link/p1872). +- [lwg 3101 - span's Container constructors need another constraint](https://cplusplus.github.io/LWG/issue3101). +- [Reddit - 2018-06 Rapperswil ISO C++ Committee Trip Report](https://www.reddit.com/r/cpp/comments/8prqzm/2018_rapperswil_iso_c_committee_trip_report/) +- [Reddit - 2018-11 San Diego ISO C++ Committee Trip Report](https://www.reddit.com/r/cpp/comments/9vwvbz/2018_san_diego_iso_c_committee_trip_report_ranges/). +- [Reddit - 2019-02 Kona ISO C++ Committee Trip Report](https://www.reddit.com/r/cpp/comments/au0c4x/201902_kona_iso_c_committee_trip_report_c20/). +- [Reddit - 2019-07 Cologne ISO C++ Committee Trip Report](https://www.reddit.com/r/cpp/comments/cfk9de/201907_cologne_iso_c_committee_trip_report_the/) +- [Reddit - 2019-11 Belfast ISO C++ Committee Trip Report](https://www.reddit.com/r/cpp/comments/dtuov8/201911_belfast_iso_c_committee_trip_report/) +- Titus Winters. [Revisiting Regular Types](https://abseil.io/blog/20180531-regular-types). Abseil Blog. 31 May 2018. + +## Appendix + +### A.1 Compile-time information + +The version of *span lite* is available via tag `[.version]`. The following tags are available for information on the compiler and on the C++ standard library used: `[.compiler]`, `[.stdc++]`, `[.stdlanguage]` and `[.stdlibrary]`. + +### A.2 Span lite test specification + +
+click to expand +

+ +```Text +span<>: Terminates construction from a nullptr and a non-zero size (C++11) +span<>: Terminates construction from two pointers in the wrong order +span<>: Terminates construction from a null pointer and a non-zero size +span<>: Terminates creation of a sub span of the first n elements for n exceeding the span +span<>: Terminates creation of a sub span of the last n elements for n exceeding the span +span<>: Terminates creation of a sub span outside the span +span<>: Terminates access outside the span +span<>: Throws on access outside the span via at(): std::out_of_range [span_FEATURE_MEMBER_AT>0][span_CONFIG_NO_EXCEPTIONS=0] +span<>: Termination throws std::logic_error-derived exception [span_CONFIG_CONTRACT_VIOLATION_THROWS=1] +span<>: Allows to default-construct +span<>: Allows to construct from a nullptr and a zero size (C++11) +span<>: Allows to construct from two pointers +span<>: Allows to construct from two iterators +span<>: Allows to construct from two iterators - empty range +span<>: Allows to construct from an iterator and a size +span<>: Allows to construct from an iterator and a size - empty range +span<>: Allows to construct from two pointers to const +span<>: Allows to construct from a non-null pointer and a size +span<>: Allows to construct from a non-null pointer to const and a size +span<>: Allows to construct from a temporary pointer and a size +span<>: Allows to construct from a temporary pointer to const and a size +span<>: Allows to construct from any pointer and a zero size (C++98) +span<>: Allows to construct from a pointer and a size via a deduction guide (C++17) +span<>: Allows to construct from an iterator and a size via a deduction guide (C++17) +span<>: Allows to construct from two iterators via a deduction guide (C++17) +span<>: Allows to construct from a C-array +span<>: Allows to construct from a C-array via a deduction guide (C++17) +span<>: Allows to construct from a const C-array +span<>: Allows to construct from a C-array with size via decay to pointer (potentially dangerous) +span<>: Allows to construct from a const C-array with size via decay to pointer (potentially dangerous) +span<>: Allows to construct from a std::initializer_list<> (C++11) +span<>: Allows to construct from a std::array<> (C++11) +span<>: Allows to construct from a std::array via a deduction guide (C++17) +span<>: Allows to construct from a std::array<> with const data (C++11, span_FEATURE_CONSTR..._ELEMENT_TYPE=1) +span<>: Allows to construct from an empty std::array<> (C++11) +span<>: Allows to construct from a container (std::vector<>) +span<>: Allows to construct from a container via a deduction guide (std::vector<>, C++17) +span<>: Allows to tag-construct from a container (std::vector<>) +span<>: Allows to tag-construct from a const container (std::vector<>) +span<>: Allows to copy-construct from another span of the same type +span<>: Allows to copy-construct from another span of a compatible type +span<>: Allows to copy-construct from a temporary span of the same type (C++11) +span<>: Allows to copy-assign from another span of the same type +span<>: Allows to copy-assign from a temporary span of the same type (C++11) +span<>: Allows to create a sub span of the first n elements +span<>: Allows to create a sub span of the last n elements +span<>: Allows to create a sub span starting at a given offset +span<>: Allows to create a sub span starting at a given offset with a given length +span<>: Allows to observe an element via array indexing +span<>: Allows to observe an element via call indexing +span<>: Allows to observe an element via at() [span_FEATURE_MEMBER_AT>0] +span<>: Allows to observe an element via data() +span<>: Allows to observe the first element via front() [span_FEATURE_MEMBER_BACK_FRONT=1] +span<>: Allows to observe the last element via back() [span_FEATURE_MEMBER_BACK_FRONT=1] +span<>: Allows to change an element via array indexing +span<>: Allows to change an element via call indexing +span<>: Allows to change an element via at() [span_FEATURE_MEMBER_AT>0] +span<>: Allows to change an element via data() +span<>: Allows to change the first element via front() [span_FEATURE_MEMBER_BACK_FRONT=1] +span<>: Allows to change the last element via back() [span_FEATURE_MEMBER_BACK_FRONT=1] +span<>: Allows to swap with another span [span_FEATURE_MEMBER_SWAP=1] +span<>: Allows forward iteration +span<>: Allows const forward iteration +span<>: Allows reverse iteration +span<>: Allows const reverse iteration +span<>: Allows to identify if a span is the same as another span [span_FEATURE_SAME=1] +span<>: Allows to compare equal to another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare unequal to another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare less than another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare less than or equal to another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare greater than another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare greater than or equal to another span of the same type [span_FEATURE_COMPARISON=1] +span<>: Allows to compare to another span of the same type and different cv-ness [span_FEATURE_SAME=0] +span<>: Allows to compare empty spans as equal [span_FEATURE_COMPARISON=1] +span<>: Allows to test for empty span via empty(), empty case +span<>: Allows to test for empty span via empty(), non-empty case +span<>: Allows to obtain the number of elements via size() +span<>: Allows to obtain the number of elements via ssize() +span<>: Allows to obtain the number of bytes via size_bytes() +span<>: Allows to view the elements as read-only bytes +span<>: Allows to view and change the elements as writable bytes +make_span() [span_FEATURE_MAKE_SPAN_TO_STD=99] +make_span(): Allows building from two pointers +make_span(): Allows building from two const pointers +make_span(): Allows building from a non-null pointer and a size +make_span(): Allows building from a non-null const pointer and a size +make_span(): Allows building from a C-array +make_span(): Allows building from a const C-array +make_span(): Allows building from a std::initializer_list<> (C++11) +make_span(): Allows building from a std::array<> (C++11) +make_span(): Allows building from a const std::array<> (C++11) +make_span(): Allows building from a container (std::vector<>) +make_span(): Allows building from a const container (std::vector<>) +make_span(): Allows building from a container (with_container_t, std::vector<>) +make_span(): Allows building from a const container (with_container_t, std::vector<>) +byte_span() [span_FEATURE_BYTE_SPAN=1] +byte_span(): Allows building a span of std::byte from a single object (C++17, byte-lite) +byte_span(): Allows building a span of const std::byte from a single const object (C++17, byte-lite) +first(), last(), subspan() [span_FEATURE_NON_MEMBER_FIRST_LAST_SUB=1] +first(): Allows to create a sub span of the first n elements +last(): Allows to create a sub span of the last n elements +subspan(): Allows to create a sub span starting at a given offset +size(): Allows to obtain the number of elements via size() +ssize(): Allows to obtain the number of elements via ssize() +tuple_size<>: Allows to obtain the number of elements via std::tuple_size<> (C++11) +tuple_element<>: Allows to obtain an element via std::tuple_element<> (C++11) +tuple_element<>: Allows to obtain an element via std::tuple_element_t<> (C++11) +get(spn): Allows to access an element via std::get<>() +tweak header: reads tweak header if supported [tweak] +``` + +

+
diff --git a/dep/span-lite/include/span.hpp b/dep/span-lite/include/span.hpp new file mode 100644 index 000000000..8ec88e0ad --- /dev/null +++ b/dep/span-lite/include/span.hpp @@ -0,0 +1,1817 @@ +// +// span for C++98 and later. +// Based on http://wg21.link/p0122r7 +// For more information see https://github.com/martinmoene/span-lite +// +// Copyright 2018-2020 Martin Moene +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef NONSTD_SPAN_HPP_INCLUDED +#define NONSTD_SPAN_HPP_INCLUDED + +#define span_lite_MAJOR 0 +#define span_lite_MINOR 9 +#define span_lite_PATCH 2 + +#define span_lite_VERSION span_STRINGIFY(span_lite_MAJOR) "." span_STRINGIFY(span_lite_MINOR) "." span_STRINGIFY(span_lite_PATCH) + +#define span_STRINGIFY( x ) span_STRINGIFY_( x ) +#define span_STRINGIFY_( x ) #x + +// span configuration: + +#define span_SPAN_DEFAULT 0 +#define span_SPAN_NONSTD 1 +#define span_SPAN_STD 2 + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define span_HAVE_TWEAK_HEADER 1 +#else +#define span_HAVE_TWEAK_HEADER 0 +//# pragma message("span.hpp: Note: Tweak header not supported.") +#endif + +// span selection and configuration: + +#define span_HAVE( feature ) ( span_HAVE_##feature ) + +#ifndef span_CONFIG_SELECT_SPAN +# define span_CONFIG_SELECT_SPAN ( span_HAVE_STD_SPAN ? span_SPAN_STD : span_SPAN_NONSTD ) +#endif + +#ifndef span_CONFIG_EXTENT_TYPE +# define span_CONFIG_EXTENT_TYPE std::size_t +#endif + +#ifndef span_CONFIG_SIZE_TYPE +# define span_CONFIG_SIZE_TYPE std::size_t +#endif + +#ifdef span_CONFIG_INDEX_TYPE +# error `span_CONFIG_INDEX_TYPE` is deprecated since v0.7.0; it is replaced by `span_CONFIG_SIZE_TYPE`. +#endif + +// span configuration (features): + +#ifndef span_FEATURE_WITH_CONTAINER +#ifdef span_FEATURE_WITH_CONTAINER_TO_STD +# define span_FEATURE_WITH_CONTAINER span_IN_STD( span_FEATURE_WITH_CONTAINER_TO_STD ) +#else +# define span_FEATURE_WITH_CONTAINER 0 +#endif +#endif + +#ifndef span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE +# define span_FEATURE_CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE 0 +#endif + +#ifndef span_FEATURE_MEMBER_AT +# define span_FEATURE_MEMBER_AT 0 +#endif + +#ifndef span_FEATURE_MEMBER_BACK_FRONT +# define span_FEATURE_MEMBER_BACK_FRONT 1 +#endif + +#ifndef span_FEATURE_MEMBER_CALL_OPERATOR +# define span_FEATURE_MEMBER_CALL_OPERATOR 0 +#endif + +#ifndef span_FEATURE_MEMBER_SWAP +# define span_FEATURE_MEMBER_SWAP 0 +#endif + +#ifndef span_FEATURE_NON_MEMBER_FIRST_LAST_SUB +# define span_FEATURE_NON_MEMBER_FIRST_LAST_SUB 0 +#endif + +#ifndef span_FEATURE_COMPARISON +# define span_FEATURE_COMPARISON 0 // Note: C++20 does not provide comparison +#endif + +#ifndef span_FEATURE_SAME +# define span_FEATURE_SAME 0 +#endif + +#if span_FEATURE_SAME && !span_FEATURE_COMPARISON +# error `span_FEATURE_SAME` requires `span_FEATURE_COMPARISON` +#endif + +#ifndef span_FEATURE_MAKE_SPAN +#ifdef span_FEATURE_MAKE_SPAN_TO_STD +# define span_FEATURE_MAKE_SPAN span_IN_STD( span_FEATURE_MAKE_SPAN_TO_STD ) +#else +# define span_FEATURE_MAKE_SPAN 0 +#endif +#endif + +#ifndef span_FEATURE_BYTE_SPAN +# define span_FEATURE_BYTE_SPAN 0 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef span_CONFIG_NO_EXCEPTIONS +# if _MSC_VER +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define span_CONFIG_NO_EXCEPTIONS 0 +# else +# define span_CONFIG_NO_EXCEPTIONS 1 +# undef span_CONFIG_CONTRACT_VIOLATION_THROWS +# undef span_CONFIG_CONTRACT_VIOLATION_TERMINATES +# define span_CONFIG_CONTRACT_VIOLATION_THROWS 0 +# define span_CONFIG_CONTRACT_VIOLATION_TERMINATES 1 +# endif +#endif + +// Control pre- and postcondition violation behaviour: + +#if defined( span_CONFIG_CONTRACT_LEVEL_ON ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#elif defined( span_CONFIG_CONTRACT_LEVEL_OFF ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x00 +#elif defined( span_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x01 +#elif defined( span_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY ) +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x10 +#else +# define span_CONFIG_CONTRACT_LEVEL_MASK 0x11 +#endif + +#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS ) +# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V span_CONFIG_CONTRACT_VIOLATION_THROWS +#else +# define span_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 +#endif + +#if defined( span_CONFIG_CONTRACT_VIOLATION_THROWS ) && span_CONFIG_CONTRACT_VIOLATION_THROWS && \ + defined( span_CONFIG_CONTRACT_VIOLATION_TERMINATES ) && span_CONFIG_CONTRACT_VIOLATION_TERMINATES +# error Please define none or one of span_CONFIG_CONTRACT_VIOLATION_THROWS and span_CONFIG_CONTRACT_VIOLATION_TERMINATES to 1, but not both. +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef span_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define span_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define span_CPLUSPLUS __cplusplus +# endif +#endif + +#define span_CPP98_OR_GREATER ( span_CPLUSPLUS >= 199711L ) +#define span_CPP11_OR_GREATER ( span_CPLUSPLUS >= 201103L ) +#define span_CPP14_OR_GREATER ( span_CPLUSPLUS >= 201402L ) +#define span_CPP17_OR_GREATER ( span_CPLUSPLUS >= 201703L ) +#define span_CPP20_OR_GREATER ( span_CPLUSPLUS >= 202000L ) + +// C++ language version (represent 98 as 3): + +#define span_CPLUSPLUS_V ( span_CPLUSPLUS / 100 - (span_CPLUSPLUS > 200000 ? 2000 : 1994) ) + +#define span_IN_STD( v ) ( ((v) == 98 ? 3 : (v)) >= span_CPLUSPLUS_V ) + +#define span_CONFIG( feature ) ( span_CONFIG_##feature ) +#define span_FEATURE( feature ) ( span_FEATURE_##feature ) +#define span_FEATURE_TO_STD( feature ) ( span_IN_STD( span_FEATURE( feature##_TO_STD ) ) ) + +// Use C++20 std::span if available and requested: + +#if span_CPP20_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define span_HAVE_STD_SPAN 1 +# else +# define span_HAVE_STD_SPAN 0 +# endif +#else +# define span_HAVE_STD_SPAN 0 +#endif + +#define span_USES_STD_SPAN ( (span_CONFIG_SELECT_SPAN == span_SPAN_STD) || ((span_CONFIG_SELECT_SPAN == span_SPAN_DEFAULT) && span_HAVE_STD_SPAN) ) + +// +// Use C++20 std::span: +// + +#if span_USES_STD_SPAN + +#include + +namespace nonstd { + +using std::span; + +// Note: C++20 does not provide comparison +// using std::operator==; +// using std::operator!=; +// using std::operator<; +// using std::operator<=; +// using std::operator>; +// using std::operator>=; +} // namespace nonstd + +#else // span_USES_STD_SPAN + +#include + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 span_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 span_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 span_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 span_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 span_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 span_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 span_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 span_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 span_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 span_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 span_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define span_COMPILER_MSVC_VER (_MSC_VER ) +# define span_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define span_COMPILER_MSVC_VER 0 +# define span_COMPILER_MSVC_VERSION 0 +#endif + +#define span_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined(__clang__) +# define span_COMPILER_CLANG_VERSION span_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define span_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define span_COMPILER_GNUC_VERSION span_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define span_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +#define span_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler warning suppression: + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wmismatched-tags" +# define span_RESTORE_WARNINGS() _Pragma( "clang diagnostic pop" ) + +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wundef" +# define span_RESTORE_WARNINGS() _Pragma( "GCC diagnostic pop" ) + +#elif span_COMPILER_MSVC_VER >= 1900 +# define span_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) +# define span_RESTORE_WARNINGS() __pragma(warning(pop )) + +// Suppress the following MSVC GSL warnings: +// - C26439, gsl::f.6 : special function 'function' can be declared 'noexcept' +// - C26440, gsl::f.6 : function 'function' can be declared 'noexcept' +// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; +// use brace initialization, gsl::narrow_cast or gsl::narrow +// - C26473: gsl::t.1 : don't cast between pointer types where the source type and the target type are the same +// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead +// - C26490: gsl::t.1 : don't use reinterpret_cast + +span_DISABLE_MSVC_WARNINGS( 26439 26440 26472 26473 26481 26490 ) + +#else +# define span_RESTORE_WARNINGS() /*empty*/ +#endif + +// Presence of language and library features: + +#ifdef _HAS_CPP0X +# define span_HAS_CPP0X _HAS_CPP0X +#else +# define span_HAS_CPP0X 0 +#endif + +#define span_CPP11_80 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1400) +#define span_CPP11_90 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1500) +#define span_CPP11_100 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1600) +#define span_CPP11_110 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1700) +#define span_CPP11_120 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1800) +#define span_CPP11_140 (span_CPP11_OR_GREATER || span_COMPILER_MSVC_VER >= 1900) + +#define span_CPP14_000 (span_CPP14_OR_GREATER) +#define span_CPP14_120 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1800) +#define span_CPP14_140 (span_CPP14_OR_GREATER || span_COMPILER_MSVC_VER >= 1900) + +#define span_CPP17_000 (span_CPP17_OR_GREATER) + +// Presence of C++11 language features: + +#define span_HAVE_ALIAS_TEMPLATE span_CPP11_140 +#define span_HAVE_AUTO span_CPP11_100 +#define span_HAVE_CONSTEXPR_11 span_CPP11_140 +#define span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG span_CPP11_120 +#define span_HAVE_EXPLICIT_CONVERSION span_CPP11_140 +#define span_HAVE_INITIALIZER_LIST span_CPP11_120 +#define span_HAVE_IS_DEFAULT span_CPP11_140 +#define span_HAVE_IS_DELETE span_CPP11_140 +#define span_HAVE_NOEXCEPT span_CPP11_140 +#define span_HAVE_NULLPTR span_CPP11_100 +#define span_HAVE_STATIC_ASSERT span_CPP11_100 + +// Presence of C++14 language features: + +#define span_HAVE_CONSTEXPR_14 span_CPP14_000 + +// Presence of C++17 language features: + +#define span_HAVE_DEPRECATED span_CPP17_000 +#define span_HAVE_NODISCARD span_CPP17_000 +#define span_HAVE_NORETURN span_CPP17_000 + +// MSVC: template parameter deduction guides since Visual Studio 2017 v15.7 + +#if defined(__cpp_deduction_guides) +# define span_HAVE_DEDUCTION_GUIDES 1 +#else +# define span_HAVE_DEDUCTION_GUIDES (span_CPP17_OR_GREATER && ! span_BETWEEN( span_COMPILER_MSVC_VER, 1, 1913 )) +#endif + +// Presence of C++ library features: + +#define span_HAVE_ADDRESSOF span_CPP17_000 +#define span_HAVE_ARRAY span_CPP11_110 +#define span_HAVE_BYTE span_CPP17_000 +#define span_HAVE_CONDITIONAL span_CPP11_120 +#define span_HAVE_CONTAINER_DATA_METHOD (span_CPP11_140 || ( span_COMPILER_MSVC_VER >= 1500 && span_HAS_CPP0X )) +#define span_HAVE_DATA span_CPP17_000 +#define span_HAVE_LONGLONG span_CPP11_80 +#define span_HAVE_REMOVE_CONST span_CPP11_110 +#define span_HAVE_SNPRINTF span_CPP11_140 +#define span_HAVE_STRUCT_BINDING span_CPP11_120 +#define span_HAVE_TYPE_TRAITS span_CPP11_90 + +// Presence of byte-lite: + +#ifdef NONSTD_BYTE_LITE_HPP +# define span_HAVE_NONSTD_BYTE 1 +#else +# define span_HAVE_NONSTD_BYTE 0 +#endif + +// C++ feature usage: + +#if span_HAVE_ADDRESSOF +# define span_ADDRESSOF(x) std::addressof(x) +#else +# define span_ADDRESSOF(x) (&x) +#endif + +#if span_HAVE_CONSTEXPR_11 +# define span_constexpr constexpr +#else +# define span_constexpr /*span_constexpr*/ +#endif + +#if span_HAVE_CONSTEXPR_14 +# define span_constexpr14 constexpr +#else +# define span_constexpr14 /*span_constexpr*/ +#endif + +#if span_HAVE_EXPLICIT_CONVERSION +# define span_explicit explicit +#else +# define span_explicit /*explicit*/ +#endif + +#if span_HAVE_IS_DELETE +# define span_is_delete = delete +#else +# define span_is_delete +#endif + +#if span_HAVE_IS_DELETE +# define span_is_delete_access public +#else +# define span_is_delete_access private +#endif + +#if span_HAVE_NOEXCEPT && ! span_CONFIG_CONTRACT_VIOLATION_THROWS_V +# define span_noexcept noexcept +#else +# define span_noexcept /*noexcept*/ +#endif + +#if span_HAVE_NULLPTR +# define span_nullptr nullptr +#else +# define span_nullptr NULL +#endif + +#if span_HAVE_DEPRECATED +# define span_deprecated(msg) [[deprecated(msg)]] +#else +# define span_deprecated(msg) /*[[deprecated]]*/ +#endif + +#if span_HAVE_NODISCARD +# define span_nodiscard [[nodiscard]] +#else +# define span_nodiscard /*[[nodiscard]]*/ +#endif + +#if span_HAVE_NORETURN +# define span_noreturn [[noreturn]] +#else +# define span_noreturn /*[[noreturn]]*/ +#endif + +// Other features: + +#define span_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG +#define span_HAVE_ITERATOR_CTOR span_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG + +// Additional includes: + +#if span_HAVE( ADDRESSOF ) +# include +#endif + +#if span_HAVE( ARRAY ) +# include +#endif + +#if span_HAVE( BYTE ) +# include +#endif + +#if span_HAVE( DATA ) +# include // for std::data(), std::size() +#endif + +#if span_HAVE( TYPE_TRAITS ) +# include +#endif + +#if ! span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) +# include +#endif + +#if span_FEATURE( MEMBER_AT ) > 1 +# include +#endif + +#if ! span_CONFIG( NO_EXCEPTIONS ) +# include +#endif + +// Contract violation + +#define span_ELIDE_CONTRACT_EXPECTS ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x01 ) ) +#define span_ELIDE_CONTRACT_ENSURES ( 0 == ( span_CONFIG_CONTRACT_LEVEL_MASK & 0x10 ) ) + +#if span_ELIDE_CONTRACT_EXPECTS +# define span_constexpr_exp span_constexpr +# define span_EXPECTS( cond ) /* Expect elided */ +#else +# define span_constexpr_exp span_constexpr14 +# define span_EXPECTS( cond ) span_CONTRACT_CHECK( "Precondition", cond ) +#endif + +#if span_ELIDE_CONTRACT_ENSURES +# define span_constexpr_ens span_constexpr +# define span_ENSURES( cond ) /* Ensures elided */ +#else +# define span_constexpr_ens span_constexpr14 +# define span_ENSURES( cond ) span_CONTRACT_CHECK( "Postcondition", cond ) +#endif + +#define span_CONTRACT_CHECK( type, cond ) \ + cond ? static_cast< void >( 0 ) \ + : nonstd::span_lite::detail::report_contract_violation( span_LOCATION( __FILE__, __LINE__ ) ": " type " violation." ) + +#ifdef __GNUG__ +# define span_LOCATION( file, line ) file ":" span_STRINGIFY( line ) +#else +# define span_LOCATION( file, line ) file "(" span_STRINGIFY( line ) ")" +#endif + +// Method enabling + +#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + +#define span_REQUIRES_0(VA) \ + template< bool B = (VA), typename std::enable_if::type = 0 > + +# if span_BETWEEN( span_COMPILER_MSVC_VERSION, 1, 140 ) +// VS 2013 and earlier seem to have trouble with SFINAE for default non-type arguments +# define span_REQUIRES_T(VA) \ + , typename = typename std::enable_if< ( VA ), nonstd::span_lite::detail::enabler >::type +# else +# define span_REQUIRES_T(VA) \ + , typename std::enable_if< (VA), int >::type = 0 +# endif + +#define span_REQUIRES_R(R, VA) \ + typename std::enable_if< (VA), R>::type + +#define span_REQUIRES_A(VA) \ + , typename std::enable_if< (VA), void*>::type = nullptr + +#else + +# define span_REQUIRES_0(VA) /*empty*/ +# define span_REQUIRES_T(VA) /*empty*/ +# define span_REQUIRES_R(R, VA) R +# define span_REQUIRES_A(VA) /*empty*/ + +#endif + +namespace nonstd { +namespace span_lite { + +// [views.constants], constants + +typedef span_CONFIG_EXTENT_TYPE extent_t; +typedef span_CONFIG_SIZE_TYPE size_t; + +span_constexpr const extent_t dynamic_extent = static_cast( -1 ); + +template< class T, extent_t Extent = dynamic_extent > +class span; + +// Tag to select span constructor taking a container (prevent ms-gsl warning C26426): + +struct with_container_t { span_constexpr with_container_t() span_noexcept {} }; +const span_constexpr with_container_t with_container; + +// C++11 emulation: + +namespace std11 { + +#if span_HAVE( REMOVE_CONST ) + +using std::remove_cv; +using std::remove_const; +using std::remove_volatile; + +#else + +template< class T > struct remove_const { typedef T type; }; +template< class T > struct remove_const< T const > { typedef T type; }; + +template< class T > struct remove_volatile { typedef T type; }; +template< class T > struct remove_volatile< T volatile > { typedef T type; }; + +template< class T > +struct remove_cv +{ + typedef typename std11::remove_volatile< typename std11::remove_const< T >::type >::type type; +}; + +#endif // span_HAVE( REMOVE_CONST ) + +#if span_HAVE( TYPE_TRAITS ) + +using std::is_same; +using std::is_signed; +using std::integral_constant; +using std::true_type; +using std::false_type; +using std::remove_reference; + +#else + +template< class T, T v > struct integral_constant { enum { value = v }; }; +typedef integral_constant< bool, true > true_type; +typedef integral_constant< bool, false > false_type; + +template< class T, class U > struct is_same : false_type{}; +template< class T > struct is_same : true_type{}; + +template< typename T > struct is_signed : false_type {}; +template<> struct is_signed : true_type {}; +template<> struct is_signed : true_type {}; +template<> struct is_signed : true_type {}; + +#endif + +} // namespace std11 + +// C++17 emulation: + +namespace std17 { + +template< bool v > struct bool_constant : std11::integral_constant{}; + +#if span_CPP11_120 + +template< class...> +using void_t = void; + +#endif + +#if span_HAVE( DATA ) + +using std::data; +using std::size; + +#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +template< typename T, std::size_t N > +inline span_constexpr auto size( const T(&)[N] ) span_noexcept -> size_t +{ + return N; +} + +template< typename C > +inline span_constexpr auto size( C const & cont ) -> decltype( cont.size() ) +{ + return cont.size(); +} + +template< typename T, std::size_t N > +inline span_constexpr auto data( T(&arr)[N] ) span_noexcept -> T* +{ + return &arr[0]; +} + +template< typename C > +inline span_constexpr auto data( C & cont ) -> decltype( cont.data() ) +{ + return cont.data(); +} + +template< typename C > +inline span_constexpr auto data( C const & cont ) -> decltype( cont.data() ) +{ + return cont.data(); +} + +template< typename E > +inline span_constexpr auto data( std::initializer_list il ) span_noexcept -> E const * +{ + return il.begin(); +} + +#endif // span_HAVE( DATA ) + +#if span_HAVE( BYTE ) +using std::byte; +#elif span_HAVE( NONSTD_BYTE ) +using nonstd::byte; +#endif + +} // namespace std17 + +// C++20 emulation: + +namespace std20 { + +#if span_HAVE( DEDUCTION_GUIDES ) +template< class T > +using iter_reference_t = decltype( *std::declval() ); +#endif + +} // namespace std20 + +// Implementation details: + +namespace detail { + +/*enum*/ struct enabler{}; + +template< typename T > +bool is_positive( T x ) +{ + return std11::is_signed::value ? x >= 0 : true; +} + +#if span_HAVE( TYPE_TRAITS ) + +template< class Q > +struct is_span_oracle : std::false_type{}; + +template< class T, span_CONFIG_EXTENT_TYPE Extent > +struct is_span_oracle< span > : std::true_type{}; + +template< class Q > +struct is_span : is_span_oracle< typename std::remove_cv::type >{}; + +template< class Q > +struct is_std_array_oracle : std::false_type{}; + +#if span_HAVE( ARRAY ) + +template< class T, std::size_t Extent > +struct is_std_array_oracle< std::array > : std::true_type{}; + +#endif + +template< class Q > +struct is_std_array : is_std_array_oracle< typename std::remove_cv::type >{}; + +template< class Q > +struct is_array : std::false_type {}; + +template< class T > +struct is_array : std::true_type {}; + +template< class T, std::size_t N > +struct is_array : std::true_type {}; + +#if span_CPP11_140 && ! span_BETWEEN( span_COMPILER_GNUC_VERSION, 1, 500 ) + +template< class, class = void > +struct has_size_and_data : std::false_type{}; + +template< class C > +struct has_size_and_data +< + C, std17::void_t< + decltype( std17::size(std::declval()) ), + decltype( std17::data(std::declval()) ) > +> : std::true_type{}; + +template< class, class, class = void > +struct is_compatible_element : std::false_type {}; + +template< class C, class E > +struct is_compatible_element +< + C, E, std17::void_t< + decltype( std17::data(std::declval()) ) > +> : std::is_convertible< typename std::remove_pointer() ) )>::type(*)[], E(*)[] >{}; + +template< class C > +struct is_container : std17::bool_constant +< + ! is_span< C >::value + && ! is_array< C >::value + && ! is_std_array< C >::value + && has_size_and_data< C >::value +>{}; + +template< class C, class E > +struct is_compatible_container : std17::bool_constant +< + is_container::value + && is_compatible_element::value +>{}; + +#else // span_CPP11_140 + +template< + class C, class E + span_REQUIRES_T(( + ! is_span< C >::value + && ! is_array< C >::value + && ! is_std_array< C >::value + && ( std::is_convertible< typename std::remove_pointer() ) )>::type(*)[], E(*)[] >::value) + // && has_size_and_data< C >::value + )) + , class = decltype( std17::size(std::declval()) ) + , class = decltype( std17::data(std::declval()) ) +> +struct is_compatible_container : std::true_type{}; + +#endif // span_CPP11_140 + +#endif // span_HAVE( TYPE_TRAITS ) + +#if ! span_CONFIG( NO_EXCEPTIONS ) +#if span_FEATURE( MEMBER_AT ) > 1 + +// format index and size: + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wlong-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wformat=ll" +# pragma GCC diagnostic ignored "-Wlong-long" +#endif + +inline void throw_out_of_range( size_t idx, size_t size ) +{ + const char fmt[] = "span::at(): index '%lli' is out of range [0..%lli)"; + char buffer[ 2 * 20 + sizeof fmt ]; + sprintf( buffer, fmt, static_cast(idx), static_cast(size) ); + + throw std::out_of_range( buffer ); +} + +#else // MEMBER_AT + +inline void throw_out_of_range( size_t /*idx*/, size_t /*size*/ ) +{ + throw std::out_of_range( "span::at(): index outside span" ); +} +#endif // MEMBER_AT +#endif // NO_EXCEPTIONS + +#if span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +struct contract_violation : std::logic_error +{ + explicit contract_violation( char const * const message ) + : std::logic_error( message ) + {} +}; + +inline void report_contract_violation( char const * msg ) +{ + throw contract_violation( msg ); +} + +#else // span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +span_noreturn inline void report_contract_violation( char const * /*msg*/ ) span_noexcept +{ + std::terminate(); +} + +#endif // span_CONFIG( CONTRACT_VIOLATION_THROWS_V ) + +} // namespace detail + +// Prevent signed-unsigned mismatch: + +#define span_sizeof(T) static_cast( sizeof(T) ) + +template< class T > +inline span_constexpr size_t to_size( T size ) +{ + return static_cast( size ); +} + +// +// [views.span] - A view over a contiguous, single-dimension sequence of objects +// +template< class T, extent_t Extent /*= dynamic_extent*/ > +class span +{ +public: + // constants and types + + typedef T element_type; + typedef typename std11::remove_cv< T >::type value_type; + + typedef T & reference; + typedef T * pointer; + typedef T const * const_pointer; + typedef T const & const_reference; + + typedef size_t size_type; + typedef extent_t extent_type; + + typedef pointer iterator; + typedef const_pointer const_iterator; + + typedef std::ptrdiff_t difference_type; + + typedef std::reverse_iterator< iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + +// static constexpr extent_type extent = Extent; + enum { extent = Extent }; + + // 26.7.3.2 Constructors, copy, and assignment [span.cons] + + span_REQUIRES_0( + ( Extent == 0 ) || + ( Extent == dynamic_extent ) + ) + span_constexpr span() span_noexcept + : data_( span_nullptr ) + , size_( 0 ) + { + // span_EXPECTS( data() == span_nullptr ); + // span_EXPECTS( size() == 0 ); + } + +#if span_HAVE( ITERATOR_CTOR ) + // Didn't yet succeed in combining the next two constructors: + + span_constexpr_exp span( std::nullptr_t, size_type count ) + : data_( span_nullptr ) + , size_( count ) + { + span_EXPECTS( data_ == span_nullptr && count == 0 ); + } + + template< typename It + span_REQUIRES_T(( + std::is_convertible()), element_type>::value + )) + > + span_constexpr_exp span( It first, size_type count ) + : data_( to_address( first ) ) + , size_( count ) + { + span_EXPECTS( + ( data_ == span_nullptr && count == 0 ) || + ( data_ != span_nullptr && detail::is_positive( count ) ) + ); + } +#else + span_constexpr_exp span( pointer ptr, size_type count ) + : data_( ptr ) + , size_( count ) + { + span_EXPECTS( + ( ptr == span_nullptr && count == 0 ) || + ( ptr != span_nullptr && detail::is_positive( count ) ) + ); + } +#endif + +#if span_HAVE( ITERATOR_CTOR ) + template< typename It, typename End + span_REQUIRES_T(( + std::is_convertible()), element_type>::value + && ! std::is_convertible::value + )) + > + span_constexpr_exp span( It first, End last ) + : data_( to_address( first ) ) + , size_( to_size( last - first ) ) + { + span_EXPECTS( + last - first >= 0 + ); + } +#else + span_constexpr_exp span( pointer first, pointer last ) + : data_( first ) + , size_( to_size( last - first ) ) + { + span_EXPECTS( + last - first >= 0 + ); + } +#endif + + template< std::size_t N + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) + > + span_constexpr span( element_type ( &arr )[ N ] ) span_noexcept + : data_( span_ADDRESSOF( arr[0] ) ) + , size_( N ) + {} + +#if span_HAVE( ARRAY ) + + template< std::size_t N + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) + > +# if span_FEATURE( CONSTRUCTION_FROM_STDARRAY_ELEMENT_TYPE ) + span_constexpr span( std::array< element_type, N > & arr ) span_noexcept +# else + span_constexpr span( std::array< value_type, N > & arr ) span_noexcept +# endif + : data_( arr.data() ) + , size_( to_size( arr.size() ) ) + {} + + template< std::size_t N +# if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == static_cast(N)) + && std::is_convertible< value_type(*)[], element_type(*)[] >::value + )) +# endif + > + span_constexpr span( std::array< value_type, N> const & arr ) span_noexcept + : data_( arr.data() ) + , size_( to_size( arr.size() ) ) + {} + +#endif // span_HAVE( ARRAY ) + +#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + template< class Container + span_REQUIRES_T(( + detail::is_compatible_container< Container, element_type >::value + )) + > + span_constexpr span( Container & cont ) + : data_( std17::data( cont ) ) + , size_( to_size( std17::size( cont ) ) ) + {} + + template< class Container + span_REQUIRES_T(( + std::is_const< element_type >::value + && detail::is_compatible_container< Container, element_type >::value + )) + > + span_constexpr span( Container const & cont ) + : data_( std17::data( cont ) ) + , size_( to_size( std17::size( cont ) ) ) + {} + +#endif // span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +#if span_FEATURE( WITH_CONTAINER ) + + template< class Container > + span_constexpr span( with_container_t, Container & cont ) + : data_( cont.size() == 0 ? span_nullptr : span_ADDRESSOF( cont[0] ) ) + , size_( to_size( cont.size() ) ) + {} + + template< class Container > + span_constexpr span( with_container_t, Container const & cont ) + : data_( cont.size() == 0 ? span_nullptr : const_cast( span_ADDRESSOF( cont[0] ) ) ) + , size_( to_size( cont.size() ) ) + {} +#endif + +#if span_HAVE( IS_DEFAULT ) + span_constexpr span( span const & other ) span_noexcept = default; + + ~span() span_noexcept = default; + + span_constexpr14 span & operator=( span const & other ) span_noexcept = default; +#else + span_constexpr span( span const & other ) span_noexcept + : data_( other.data_ ) + , size_( other.size_ ) + {} + + ~span() span_noexcept + {} + + span_constexpr14 span & operator=( span const & other ) span_noexcept + { + data_ = other.data_; + size_ = other.size_; + + return *this; + } +#endif + + template< class OtherElementType, extent_type OtherExtent + span_REQUIRES_T(( + (Extent == dynamic_extent || Extent == OtherExtent) + && std::is_convertible::value + )) + > + span_constexpr_exp span( span const & other ) span_noexcept + : data_( reinterpret_cast( other.data() ) ) + , size_( other.size() ) + { + span_EXPECTS( OtherExtent == dynamic_extent || other.size() == to_size(OtherExtent) ); + } + + // 26.7.3.3 Subviews [span.sub] + + template< extent_type Count > + span_constexpr_exp span< element_type, Count > + first() const + { + span_EXPECTS( detail::is_positive( Count ) && Count <= size() ); + + return span< element_type, Count >( data(), Count ); + } + + template< extent_type Count > + span_constexpr_exp span< element_type, Count > + last() const + { + span_EXPECTS( detail::is_positive( Count ) && Count <= size() ); + + return span< element_type, Count >( data() + (size() - Count), Count ); + } + +#if span_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) + template< size_type Offset, extent_type Count = dynamic_extent > +#else + template< size_type Offset, extent_type Count /*= dynamic_extent*/ > +#endif + span_constexpr_exp span< element_type, Count > + subspan() const + { + span_EXPECTS( + ( detail::is_positive( Offset ) && Offset <= size() ) && + ( Count == dynamic_extent || (detail::is_positive( Count ) && Count + Offset <= size()) ) + ); + + return span< element_type, Count >( + data() + Offset, Count != dynamic_extent ? Count : (Extent != dynamic_extent ? Extent - Offset : size() - Offset) ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + first( size_type count ) const + { + span_EXPECTS( detail::is_positive( count ) && count <= size() ); + + return span< element_type, dynamic_extent >( data(), count ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + last( size_type count ) const + { + span_EXPECTS( detail::is_positive( count ) && count <= size() ); + + return span< element_type, dynamic_extent >( data() + ( size() - count ), count ); + } + + span_constexpr_exp span< element_type, dynamic_extent > + subspan( size_type offset, size_type count = static_cast(dynamic_extent) ) const + { + span_EXPECTS( + ( ( detail::is_positive( offset ) && offset <= size() ) ) && + ( count == static_cast(dynamic_extent) || ( detail::is_positive( count ) && offset + count <= size() ) ) + ); + + return span< element_type, dynamic_extent >( + data() + offset, count == static_cast(dynamic_extent) ? size() - offset : count ); + } + + // 26.7.3.4 Observers [span.obs] + + span_constexpr size_type size() const span_noexcept + { + return size_; + } + + span_constexpr std::ptrdiff_t ssize() const span_noexcept + { + return static_cast( size_ ); + } + + span_constexpr size_type size_bytes() const span_noexcept + { + return size() * to_size( sizeof( element_type ) ); + } + + span_nodiscard span_constexpr bool empty() const span_noexcept + { + return size() == 0; + } + + // 26.7.3.5 Element access [span.elem] + + span_constexpr_exp reference operator[]( size_type idx ) const + { + span_EXPECTS( detail::is_positive( idx ) && idx < size() ); + + return *( data() + idx ); + } + +#if span_FEATURE( MEMBER_CALL_OPERATOR ) + span_deprecated("replace operator() with operator[]") + + span_constexpr_exp reference operator()( size_type idx ) const + { + span_EXPECTS( detail::is_positive( idx ) && idx < size() ); + + return *( data() + idx ); + } +#endif + +#if span_FEATURE( MEMBER_AT ) + span_constexpr14 reference at( size_type idx ) const + { +#if span_CONFIG( NO_EXCEPTIONS ) + return this->operator[]( idx ); +#else + if ( !detail::is_positive( idx ) || size() <= idx ) + { + detail::throw_out_of_range( idx, size() ); + } + return *( data() + idx ); +#endif + } +#endif + + span_constexpr pointer data() const span_noexcept + { + return data_; + } + +#if span_FEATURE( MEMBER_BACK_FRONT ) + + span_constexpr_exp reference front() const span_noexcept + { + span_EXPECTS( ! empty() ); + + return *data(); + } + + span_constexpr_exp reference back() const span_noexcept + { + span_EXPECTS( ! empty() ); + + return *( data() + size() - 1 ); + } + +#endif + + // xx.x.x.x Modifiers [span.modifiers] + +#if span_FEATURE( MEMBER_SWAP ) + + span_constexpr14 void swap( span & other ) span_noexcept + { + using std::swap; + swap( data_, other.data_ ); + swap( size_, other.size_ ); + } +#endif + + // 26.7.3.6 Iterator support [span.iterators] + + span_constexpr iterator begin() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() }; +#else + return iterator( data() ); +#endif + } + + span_constexpr iterator end() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() + size() }; +#else + return iterator( data() + size() ); +#endif + } + + span_constexpr const_iterator cbegin() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() }; +#else + return const_iterator( data() ); +#endif + } + + span_constexpr const_iterator cend() const span_noexcept + { +#if span_CPP11_OR_GREATER + return { data() + size() }; +#else + return const_iterator( data() + size() ); +#endif + } + + span_constexpr reverse_iterator rbegin() const span_noexcept + { + return reverse_iterator( end() ); + } + + span_constexpr reverse_iterator rend() const span_noexcept + { + return reverse_iterator( begin() ); + } + + span_constexpr const_reverse_iterator crbegin() const span_noexcept + { + return const_reverse_iterator ( cend() ); + } + + span_constexpr const_reverse_iterator crend() const span_noexcept + { + return const_reverse_iterator( cbegin() ); + } + +private: + + // Note: C++20 has std::pointer_traits::to_address( it ); + +#if span_HAVE( ITERATOR_CTOR ) + static inline span_constexpr pointer to_address( std::nullptr_t ) span_noexcept + { + return nullptr; + } + + template< typename U > + static inline span_constexpr U * to_address( U * p ) span_noexcept + { + return p; + } + + template< typename Ptr + span_REQUIRES_T(( ! std::is_pointer::value )) + > + static inline span_constexpr pointer to_address( Ptr const & it ) span_noexcept + { + return to_address( it.operator->() ); + } +#endif // span_HAVE( ITERATOR_CTOR ) + +private: + pointer data_; + size_type size_; +}; + +// class template argument deduction guides: + +#if span_HAVE( DEDUCTION_GUIDES ) + +template< class T, size_t N > +span( T (&)[N] ) -> span(N)>; + +template< class T, size_t N > +span( std::array & ) -> span(N)>; + +template< class T, size_t N > +span( std::array const & ) -> span(N)>; + +#if span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) + +template< class Container > +span( Container& ) -> span; + +template< class Container > +span( Container const & ) -> span; + +#endif + +// iterator: constraints: It satisfies contiguous_­iterator. + +template< class It, class EndOrSize > +span( It, EndOrSize ) -> span< typename std11::remove_reference< typename std20::iter_reference_t >::type >; + +#endif // span_HAVE( DEDUCTION_GUIDES ) + +// 26.7.3.7 Comparison operators [span.comparison] + +#if span_FEATURE( COMPARISON ) +#if span_FEATURE( SAME ) + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool same( span const & l, span const & r ) span_noexcept +{ + return std11::is_same::value + && l.size() == r.size() + && static_cast( l.data() ) == r.data(); +} + +#endif + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator==( span const & l, span const & r ) +{ + return +#if span_FEATURE( SAME ) + same( l, r ) || +#endif + ( l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ) ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator<( span const & l, span const & r ) +{ + return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator!=( span const & l, span const & r ) +{ + return !( l == r ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator<=( span const & l, span const & r ) +{ + return !( r < l ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator>( span const & l, span const & r ) +{ + return ( r < l ); +} + +template< class T1, extent_t E1, class T2, extent_t E2 > +inline span_constexpr bool operator>=( span const & l, span const & r ) +{ + return !( l < r ); +} + +#endif // span_FEATURE( COMPARISON ) + +// 26.7.2.6 views of object representation [span.objectrep] + +#if span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) + +// Avoid MSVC 14.1 (1910), VS 2017: warning C4307: '*': integral constant overflow: + +template< typename T, extent_t Extent > +struct BytesExtent +{ +#if span_CPP11_OR_GREATER + enum ET : extent_t { value = span_sizeof(T) * Extent }; +#else + enum ET { value = span_sizeof(T) * Extent }; +#endif +}; + +template< typename T > +struct BytesExtent< T, dynamic_extent > +{ +#if span_CPP11_OR_GREATER + enum ET : extent_t { value = dynamic_extent }; +#else + enum ET { value = dynamic_extent }; +#endif +}; + +template< class T, extent_t Extent > +inline span_constexpr span< const std17::byte, BytesExtent::value > +as_bytes( span spn ) span_noexcept +{ +#if 0 + return { reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() }; +#else + return span< const std17::byte, BytesExtent::value >( + reinterpret_cast< std17::byte const * >( spn.data() ), spn.size_bytes() ); // NOLINT +#endif +} + +template< class T, extent_t Extent > +inline span_constexpr span< std17::byte, BytesExtent::value > +as_writable_bytes( span spn ) span_noexcept +{ +#if 0 + return { reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() }; +#else + return span< std17::byte, BytesExtent::value >( + reinterpret_cast< std17::byte * >( spn.data() ), spn.size_bytes() ); // NOLINT +#endif +} + +#endif // span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) + +// extensions: non-member views: +// this feature implies the presence of make_span() + +#if span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) && span_CPP11_120 + +template< extent_t Count, class T > +span_constexpr auto +first( T & t ) -> decltype( make_span(t).template first() ) +{ + return make_span( t ).template first(); +} + +template< class T > +span_constexpr auto +first( T & t, size_t count ) -> decltype( make_span(t).first(count) ) +{ + return make_span( t ).first( count ); +} + +template< extent_t Count, class T > +span_constexpr auto +last( T & t ) -> decltype( make_span(t).template last() ) +{ + return make_span(t).template last(); +} + +template< class T > +span_constexpr auto +last( T & t, extent_t count ) -> decltype( make_span(t).last(count) ) +{ + return make_span( t ).last( count ); +} + +template< size_t Offset, extent_t Count = dynamic_extent, class T > +span_constexpr auto +subspan( T & t ) -> decltype( make_span(t).template subspan() ) +{ + return make_span( t ).template subspan(); +} + +template< class T > +span_constexpr auto +subspan( T & t, size_t offset, extent_t count = dynamic_extent ) -> decltype( make_span(t).subspan(offset, count) ) +{ + return make_span( t ).subspan( offset, count ); +} + +#endif // span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) + +// 27.8 Container and view access [iterator.container] + +template< class T, extent_t Extent /*= dynamic_extent*/ > +span_constexpr std::size_t size( span const & spn ) +{ + return static_cast( spn.size() ); +} + +template< class T, extent_t Extent /*= dynamic_extent*/ > +span_constexpr std::ptrdiff_t ssize( span const & spn ) +{ + return static_cast( spn.size() ); +} + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { + +using span_lite::dynamic_extent; + +using span_lite::span; + +using span_lite::with_container; + +#if span_FEATURE( COMPARISON ) +#if span_FEATURE( SAME ) +using span_lite::same; +#endif + +using span_lite::operator==; +using span_lite::operator!=; +using span_lite::operator<; +using span_lite::operator<=; +using span_lite::operator>; +using span_lite::operator>=; +#endif + +#if span_HAVE( BYTE ) +using span_lite::as_bytes; +using span_lite::as_writable_bytes; +#endif + +using span_lite::size; +using span_lite::ssize; + +} // namespace nonstd + +#endif // span_USES_STD_SPAN + +// make_span() [span-lite extension]: + +#if span_FEATURE( MAKE_SPAN ) || span_FEATURE( NON_MEMBER_FIRST_LAST_SUB ) + +#if span_USES_STD_SPAN +# define span_constexpr constexpr +# define span_noexcept noexcept +# define span_nullptr nullptr +# ifndef span_CONFIG_EXTENT_TYPE +# define span_CONFIG_EXTENT_TYPE std::size_t +# endif +using extent_t = span_CONFIG_EXTENT_TYPE; +#endif // span_USES_STD_SPAN + +namespace nonstd { +namespace span_lite { + +template< class T > +inline span_constexpr span +make_span( T * ptr, size_t count ) span_noexcept +{ + return span( ptr, count ); +} + +template< class T > +inline span_constexpr span +make_span( T * first, T * last ) span_noexcept +{ + return span( first, last ); +} + +template< class T, std::size_t N > +inline span_constexpr span(N)> +make_span( T ( &arr )[ N ] ) span_noexcept +{ + return span(N)>( &arr[ 0 ], N ); +} + +#if span_USES_STD_SPAN || span_HAVE( ARRAY ) + +template< class T, std::size_t N > +inline span_constexpr span(N)> +make_span( std::array< T, N > & arr ) span_noexcept +{ + return span(N)>( arr ); +} + +template< class T, std::size_t N > +inline span_constexpr span< const T, static_cast(N) > +make_span( std::array< T, N > const & arr ) span_noexcept +{ + return span(N)>( arr ); +} + +#endif // span_HAVE( ARRAY ) + +#if span_USES_STD_SPAN + +template< class Container, class EP = decltype( std::data(std::declval())) > +inline span_constexpr auto +make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer::type > +{ + return span< typename std::remove_pointer::type >( cont ); +} + +template< class Container, class EP = decltype( std::data(std::declval())) > +inline span_constexpr auto +make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer::type > +{ + return span< const typename std::remove_pointer::type >( cont ); +} + +#elif span_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) && span_HAVE( AUTO ) + +template< class Container, class EP = decltype( std17::data(std::declval())) > +inline span_constexpr auto +make_span( Container & cont ) span_noexcept -> span< typename std::remove_pointer::type > +{ + return span< typename std::remove_pointer::type >( cont ); +} + +template< class Container, class EP = decltype( std17::data(std::declval())) > +inline span_constexpr auto +make_span( Container const & cont ) span_noexcept -> span< const typename std::remove_pointer::type > +{ + return span< const typename std::remove_pointer::type >( cont ); +} + +#else + +template< class T > +inline span_constexpr span +make_span( span spn ) span_noexcept +{ + return spn; +} + +template< class T, class Allocator > +inline span_constexpr span +make_span( std::vector & cont ) span_noexcept +{ + return span( with_container, cont ); +} + +template< class T, class Allocator > +inline span_constexpr span +make_span( std::vector const & cont ) span_noexcept +{ + return span( with_container, cont ); +} + +#endif // span_USES_STD_SPAN || ( ... ) + +#if ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER ) + +template< class Container > +inline span_constexpr span +make_span( with_container_t, Container & cont ) span_noexcept +{ + return span< typename Container::value_type >( with_container, cont ); +} + +template< class Container > +inline span_constexpr span +make_span( with_container_t, Container const & cont ) span_noexcept +{ + return span< const typename Container::value_type >( with_container, cont ); +} + +#endif // ! span_USES_STD_SPAN && span_FEATURE( WITH_CONTAINER ) + + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { +using span_lite::make_span; +} // namespace nonstd + +#endif // #if span_FEATURE_TO_STD( MAKE_SPAN ) + +#if span_CPP11_OR_GREATER && span_FEATURE( BYTE_SPAN ) && ( span_HAVE( BYTE ) || span_HAVE( NONSTD_BYTE ) ) + +namespace nonstd { +namespace span_lite { + +template< class T > +inline span_constexpr auto +byte_span( T & t ) span_noexcept -> span< std17::byte, span_sizeof(T) > +{ + return span< std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte * >( &t ), span_sizeof(T) ); +} + +template< class T > +inline span_constexpr auto +byte_span( T const & t ) span_noexcept -> span< const std17::byte, span_sizeof(T) > +{ + return span< const std17::byte, span_sizeof(t) >( reinterpret_cast< std17::byte const * >( &t ), span_sizeof(T) ); +} + +} // namespace span_lite +} // namespace nonstd + +// make available in nonstd: + +namespace nonstd { +using span_lite::byte_span; +} // namespace nonstd + +#endif // span_FEATURE( BYTE_SPAN ) + +#if span_HAVE( STRUCT_BINDING ) + +#if span_CPP14_OR_GREATER +# include +#elif span_CPP11_OR_GREATER +# include +namespace std { + template< std::size_t I, typename T > + using tuple_element_t = typename tuple_element::type; +} +#else +namespace std { + template< typename T > + class tuple_size; /*undefined*/ + + template< std::size_t I, typename T > + class tuple_element; /* undefined */ +} +#endif // span_CPP14_OR_GREATER + +namespace std { + +// 26.7.X Tuple interface + +// std::tuple_size<>: + +template< typename ElementType, nonstd::span_lite::extent_t Extent > +class tuple_size< nonstd::span > : public integral_constant(Extent)> {}; + +// std::tuple_size<>: Leave undefined for dynamic extent: + +template< typename ElementType > +class tuple_size< nonstd::span >; + +// std::tuple_element<>: + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +class tuple_element< I, nonstd::span > +{ +public: +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "tuple_element: dynamic extent or index out of range" ); +#endif + using type = ElementType; +}; + +// std::get<>(), 2 variants: + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +span_constexpr ElementType & get( nonstd::span & spn ) span_noexcept +{ +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" ); +#endif + return spn[I]; +} + +template< size_t I, typename ElementType, nonstd::span_lite::extent_t Extent > +span_constexpr ElementType const & get( nonstd::span const & spn ) span_noexcept +{ +#if span_HAVE( STATIC_ASSERT ) + static_assert( Extent != nonstd::dynamic_extent && I < Extent, "get<>(span): dynamic extent or index out of range" ); +#endif + return spn[I]; +} + +} // end namespace std + +#endif // span_HAVE( STRUCT_BINDING ) + +#if ! span_USES_STD_SPAN +span_RESTORE_WARNINGS() +#endif // span_USES_STD_SPAN + +#endif // NONSTD_SPAN_HPP_INCLUDED diff --git a/distros/beowulf/control b/distros/beowulf/control index dd410c0ba..28135f583 100644 --- a/distros/beowulf/control +++ b/distros/beowulf/control @@ -5,7 +5,6 @@ Maintainer: Isaac Connor Uploaders: Isaac Connor Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,cmake - ,libx264-dev, libmp4v2-dev ,libavdevice-dev ,libavcodec-dev ,libavformat-dev @@ -34,7 +33,6 @@ Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,libdata-entropy-perl # Unbundled (dh_linktree): ,libjs-jquery - ,libjs-mootools Standards-Version: 3.9.8 Homepage: http://www.zoneminder.com/ Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git @@ -44,7 +42,6 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libmp4v2-2, libx264-155 ,libswscale5 ,libswresample3 ,ffmpeg diff --git a/distros/beowulf/copyright b/distros/beowulf/copyright index c48025a25..a932efa2f 100644 --- a/distros/beowulf/copyright +++ b/distros/beowulf/copyright @@ -9,7 +9,6 @@ Comment: on Fri, 8 Dec 2006 10:19:43 +1100 Files-Excluded: web/skins/*/js/jquery-* - web/tools/mootools/*-yc.js Files: * Copyright: 2001-2014 Philip Coombes @@ -37,11 +36,6 @@ Comment: Includes Sizzle.js http://sizzlejs.com/ Released under the MIT, BSD, and GPL Licenses. -Files: web/tools/mootools/*.js -Copyright: 2009 Marcelo Jorge Vieira (metal) - 2006-2010 Valerio Proietti (http://mad4milk.net/) -License: Expat - Files: web/api/* Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat diff --git a/distros/beowulf/source/lintian-overrides b/distros/beowulf/source/lintian-overrides index 3669e5de8..f905a5a2f 100644 --- a/distros/beowulf/source/lintian-overrides +++ b/distros/beowulf/source/lintian-overrides @@ -1,9 +1,5 @@ -## Actually sources are there: "*-nc.js". -source-is-missing web/tools/mootools/mootools-*-yc.js - ## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-1.4.2.min.js +source-is-missing web/skins/*/js/jquery-3.5.1.min.js ## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/tools/mootools/mootools-*-yc.js -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-1.4.2.min.js +source-contains-prebuilt-javascript-object web/skins/*/js/jquery-3.5.1.min.js diff --git a/distros/beowulf/zoneminder.linktrees b/distros/beowulf/zoneminder.linktrees index 2e843bbf1..8a9ca2723 100644 --- a/distros/beowulf/zoneminder.linktrees +++ b/distros/beowulf/zoneminder.linktrees @@ -1,14 +1,6 @@ ## cakephp #replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake -## libjs-mootools -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-nc.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-yc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-nc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-yc.js - ## libjs-jquery -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-1.4.2.min.js -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-1.4.2.min.js +replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-3.5.1.min.js +replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-3.5.1.min.js diff --git a/distros/beowulf/zoneminder.postinst b/distros/beowulf/zoneminder.postinst index 603786ff6..032595355 100644 --- a/distros/beowulf/zoneminder.postinst +++ b/distros/beowulf/zoneminder.postinst @@ -39,9 +39,9 @@ if [ "$1" = "configure" ]; then exit 1; fi # This creates the user. - echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute, REFERENCES on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql else - echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + echo "grant lock tables, alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute, REFERENCES on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql fi zmupdate.pl --nointeractive diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index 45cafe57d..e3e05c54e 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -7,12 +7,12 @@ # Display a message to show the RHEL build options are being processed. if(ZM_TARGET_DISTRO MATCHES "^el") - message([STATUS] "Starting RHEL Build Options" ...) + message([STATUS] "Starting RHEL Build Options" ...) elseif(ZM_TARGET_DISTRO MATCHES "^fc") - message([STATUS] "Starting Fedora Build Options" ...) -else(ZM_TARGET_DISTRO MATCHES "^el") - message([WARNING] "Unknown Build Option Detected" ...) -endif(ZM_TARGET_DISTRO MATCHES "^el") + message([STATUS] "Starting Fedora Build Options" ...) +else() + message([WARNING] "Unknown Build Option Detected" ...) +endif() # # CONFIGURE STAGE diff --git a/distros/redhat/readme/README.httpd b/distros/redhat/readme/README.httpd index 1ba0ee688..32004a92d 100644 --- a/distros/redhat/readme/README.httpd +++ b/distros/redhat/readme/README.httpd @@ -15,9 +15,11 @@ NOTE: EL7 users should replace "dnf" with "yum" in the instructions below. set during the previous step, you will need to create the ZoneMinder database and configure a database account for ZoneMinder to use: - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant all on zm.* to \ - 'zmuser'@localhost identified by 'zmpass';" + mysql -u root -p < /usr/share/zoneminder/db/zm_create.sql + mysql -u root -p -e "CREATE USER 'zmuser'@'localhost' \ + IDENTIFIED BY 'zmpass';" + mysql -u root -p -e "GRANT ALL PRIVILEGES ON zm.* TO \ + 'zmuser'@localhost;" mysqladmin -uroot -p reload The database account credentials, zmuser/zmpass, are arbitrary. Set them to diff --git a/distros/redhat/readme/README.nginx b/distros/redhat/readme/README.nginx index 8bc3bbdc1..2a15d9544 100644 --- a/distros/redhat/readme/README.nginx +++ b/distros/redhat/readme/README.nginx @@ -13,9 +13,11 @@ New installs set during the previous step, you will need to create the ZoneMinder database and configure a database account for ZoneMinder to use: - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant all on zm.* to \ - 'zmuser'@localhost identified by 'zmpass';" + mysql -u root -p < /usr/share/zoneminder/db/zm_create.sql + mysql -u root -p -e "CREATE USER 'zmuser'@'localhost' \ + IDENTIFIED BY 'zmpass';" + mysql -u root -p -e "GRANT ALL PRIVILEGES ON zm.* TO \ + 'zmuser'@localhost;" mysqladmin -uroot -p reload The database account credentials, zmuser/zmpass, are arbitrary. Set them to @@ -44,10 +46,7 @@ New installs 5. Disable SELinux - We currently do not have the resources to create and maintain an accurate - SELinux policy for ZoneMinder on Fedora. We will gladly accept pull - reqeusts from anyone who wishes to do the work. In the meantime, SELinux - will need to be disabled or put into permissive mode. + SELinux must be disabled or put into permissive mode. This is not optional! To immediately disbale SELinux for the current seesion, issue the following from the command line: @@ -78,11 +77,20 @@ New installs sudo ln -sf /etc/zm/www/zoneminder.nginx.conf /etc/nginx/conf.d/ sudo ln -sf /etc/zm/www/redirect.nginx.conf /etc/nginx/default.d/ -7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of +7. Configure and start fcgiwrap + + Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of simulatneous streams the server should support. Generally, a good minimum value for this equals the total number of cameras you expect to view at the same time. + Enable the fcgiwrap *socket* in the following manner: + + sudo systemctl enable --now fcgiwrap@nginx.socket + + Do NOT try to start the fcgiwrap service! It must be triggered by the + socket to work properly. + 8. Now start the web server: sudo systemctl enable nginx diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 9e876324e..8c0773911 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -3,11 +3,14 @@ %global zmgid_final apache # Crud is configured as a git submodule -%global crud_version 3.1.0-zm +%global crud_version 3.2.0 # CakePHP-Enum-Behavior is configured as a git submodule %global ceb_version 1.0-zm +# RtspServer is configured as a git submodule +%global rtspserver_commit cd7fd49becad6010a1b8466bfebbd93999a39878 + %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt %global sslkey %{_sysconfdir}/pki/tls/private/localhost.key @@ -28,11 +31,10 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.35.6 +Version: 1.35.28 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons -# Mootools is under the MIT license: http://mootools.net/ # jQuery is under the MIT license: https://jquery.org/license/ # CakePHP is under the MIT license: https://github.com/cakephp/cakephp # Crud is under the MIT license: https://github.com/FriendsOfCake/crud @@ -40,12 +42,14 @@ Group: System Environment/Daemons # Bootstrap is under the MIT license: https://getbootstrap.com/docs/4.5/about/license/ # Bootstrap-table is under the MIT license: https://bootstrap-table.com/docs/about/license/ # font-awesome is under CC-BY license: https://fontawesome.com/license/free +# RtspServer is under the MIT license: https://github.com/PHZ76/RtspServer/blob/master/LICENSE License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz -Source1: https://github.com/ZoneMinder/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz +Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz Source2: https://github.com/ZoneMinder/CakePHP-Enum-Behavior/archive/%{ceb_version}.tar.gz#/cakephp-enum-behavior-%{ceb_version}.tar.gz +Source3: https://github.com/ZoneMinder/RtspServer/archive/%{rtspserver_commit}.tar.gz#/RtspServer-%{rtspserver_commit}.tar.gz %{?rhel:BuildRequires: epel-rpm-macros} BuildRequires: systemd-devel @@ -89,10 +93,6 @@ BuildRequires: zlib-devel BuildRequires: ffmpeg BuildRequires: ffmpeg-devel -# Required for mp4 container support -BuildRequires: libmp4v2-devel -BuildRequires: x264-devel - # Allow existing user base to seamlessly transition to sub-packages Requires: %{name}-common%{?_isa} = %{version}-%{release} Requires: %{name}-httpd%{?_isa} = %{version}-%{release} @@ -202,12 +202,14 @@ gzip -dc %{_sourcedir}/cakephp-enum-behavior-%{ceb_version}.tar.gz | tar -xvvf - rm -rf ./web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Behavior +gzip -dc %{_sourcedir}/RtspServer-%{rtspserver_commit}.tar.gz | tar -xvvf - +rm -rf ./dep/RtspServer +mv -f RtspServer-%{rtspserver_commit} ./dep/RtspServer + # Change the following default values ./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes ./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes ./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no -./utils/zmeditconfigdata.sh ZM_OPT_FAST_DELETE no %build # Disable LTO due to top level asm @@ -263,6 +265,32 @@ fi echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, you must read the README file\nto finish the installation or upgrade!" echo -e "\nThe README file is located here: %{_pkgdocdir}-common/README\n" +# Neither the Apache nor Nginx packages create an SSL certificate anymore, so lets do that here +if [ -f %{sslkey} -o -f %{sslcert} ]; then + exit 0 +fi + +umask 077 +%{_bindir}/openssl genrsa -rand /proc/cpuinfo:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/uptime 2048 > %{sslkey} 2> /dev/null + +FQDN=`hostname` +# A >59 char FQDN means "root@FQDN" exceeds 64-char max length for emailAddress +if [ "x${FQDN}" = "x" -o ${#FQDN} -gt 59 ]; then + FQDN=localhost.localdomain +fi + +cat << EOF | %{_bindir}/openssl req -new -key %{sslkey} \ + -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req \ + -out %{sslcert} 2>/dev/null +-- +SomeState +SomeCity +SomeOrganization +SomeOrganizationalUnit +${FQDN} +root@${FQDN} +EOF + %post httpd # For the case of changing from nginx <-> httpd, files in these folders must change ownership if they exist %{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || : @@ -294,32 +322,6 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %{_bindir}/gpasswd -a nginx video >/dev/null 2>&1 || : %{_bindir}/gpasswd -a nginx dialout >/dev/null 2>&1 || : -# Nginx does not create an SSL certificate like the apache package does so lets do that here -if [ -f %{sslkey} -o -f %{sslcert} ]; then - exit 0 -fi - -umask 077 -%{_bindir}/openssl genrsa -rand /proc/apm:/proc/cpuinfo:/proc/dma:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/pci:/proc/rtc:/proc/uptime 2048 > %{sslkey} 2> /dev/null - -FQDN=`hostname` -# A >59 char FQDN means "root@FQDN" exceeds 64-char max length for emailAddress -if [ "x${FQDN}" = "x" -o ${#FQDN} -gt 59 ]; then - FQDN=localhost.localdomain -fi - -cat << EOF | %{_bindir}/openssl req -new -key %{sslkey} \ - -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req \ - -out %{sslcert} 2>/dev/null --- -SomeState -SomeCity -SomeOrganization -SomeOrganizationalUnit -${FQDN} -root@${FQDN} -EOF - %preun %systemd_preun %{name}.service @@ -348,7 +350,6 @@ EOF %{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy %{_bindir}/zmsystemctl.pl -%{_bindir}/zma %{_bindir}/zmaudit.pl %{_bindir}/zmc %{_bindir}/zmcontrol.pl @@ -368,6 +369,7 @@ EOF %{_bindir}/zmonvif-trigger.pl %{_bindir}/zmstats.pl %{_bindir}/zmrecover.pl +%{_bindir}/zm_rtsp_server %{perl_vendorlib}/ZoneMinder* %{perl_vendorlib}/ONVIF* @@ -423,6 +425,10 @@ EOF %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder %changelog +* Wed Apr 07 2021 Andrew Bauer - 1.35.23-1 +- 1.35.23 Development snapshot +- Build against rtspserver + * Tue Feb 04 2020 Andrew Bauer - 1.34.2-1 - 1.34.2 Release diff --git a/distros/ubuntu1504_cmake_split_packages/control b/distros/ubuntu1504_cmake_split_packages/control index b24d67cf2..d4be60413 100644 --- a/distros/ubuntu1504_cmake_split_packages/control +++ b/distros/ubuntu1504_cmake_split_packages/control @@ -16,7 +16,7 @@ Build-Depends: debhelper (>= 9), po-debconf (>= 1.0), autoconf, automake, libtoo , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev , libgcrypt11-dev | libgcrypt20-dev, libpolkit-gobject-1-dev -, libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl, libx264-dev, libmp4v2-dev +, libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl Standards-Version: 3.9.6 Homepage: http://www.zoneminder.com/ @@ -75,7 +75,7 @@ Depends: libzoneminder-perl (= ${source:Version}), libdbi-perl, libmodule-load-conditional-perl, libmime-lite-perl, libmime-tools-perl, libnet-sftp-foreign-perl, libphp-serialization-perl, debconf, ffmpeg | libav-tools, rsyslog | system-log-daemon, zip, - policykit-1, apache2, libmp4v2-2, libpcre++0 + policykit-1, apache2, libpcre++0 , libsys-cpu-perl, libsys-meminfo-perl , libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl Description: Core binaries and perl scripts for ZoneMinder diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index f6169c495..4cf1e0c58 100755 --- a/distros/ubuntu1504_cmake_split_packages/rules +++ b/distros/ubuntu1504_cmake_split_packages/rules @@ -15,7 +15,6 @@ DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) CFLAGS = -Wall -CPPFLAGS = -D__STDC_CONSTANT_MACROS CXXFLAGS = -DHAVE_LIBCRYPTO ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) @@ -73,7 +72,7 @@ override_dh_auto_test: override_dh_clean: # Add here commands to clean up after the build process. [ ! -f Makefile ] || $(MAKE) distclean - dh_clean src/zm_config_defines.h + dh_clean zm_config_defines.h # # Delete remaining auto-generated Makefile if Makefile.in exists find $(CURDIR)/ -type f -name "Makefile" | while read file; do \ diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 34af7cef1..0def73f60 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -3,9 +3,8 @@ Section: net Priority: optional Maintainer: Isaac Connor Uploaders: Isaac Connor -Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2 +Build-Depends: debhelper (>= 9), dh-systemd, python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2 ,cmake - ,libx264-dev, libmp4v2-dev ,libavdevice-dev (>= 6:10~) ,libavcodec-dev (>= 6:10~) ,libavformat-dev (>= 6:10~) @@ -34,9 +33,6 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libcrypt-eksblowfish-perl ,libdata-entropy-perl ,libvncserver-dev -# Unbundled (dh_linktree): - ,libjs-jquery - ,libjs-mootools Standards-Version: 3.9.8 Homepage: http://www.zoneminder.com/ Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git @@ -46,7 +42,6 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libmp4v2-2, libx264-142|libx264-148|libx264-152|libx264-155 ,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 ,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1 ,ffmpeg | libav-tools @@ -134,7 +129,7 @@ Package: zoneminder-doc Section: doc Architecture: all Multi-Arch: foreign -Depends: ${misc:Depends}, ${sphinxdoc:Depends}, python-sphinx-rtd-theme | python3-sphinx-rtd-theme +Depends: ${misc:Depends}, ${sphinxdoc:Depends}, python3-sphinx-rtd-theme Suggests: www-browser Description: ZoneMinder documentation ZoneMinder is intended for use in single or multi-camera video security diff --git a/distros/ubuntu1604/copyright b/distros/ubuntu1604/copyright index c48025a25..a932efa2f 100644 --- a/distros/ubuntu1604/copyright +++ b/distros/ubuntu1604/copyright @@ -9,7 +9,6 @@ Comment: on Fri, 8 Dec 2006 10:19:43 +1100 Files-Excluded: web/skins/*/js/jquery-* - web/tools/mootools/*-yc.js Files: * Copyright: 2001-2014 Philip Coombes @@ -37,11 +36,6 @@ Comment: Includes Sizzle.js http://sizzlejs.com/ Released under the MIT, BSD, and GPL Licenses. -Files: web/tools/mootools/*.js -Copyright: 2009 Marcelo Jorge Vieira (metal) - 2006-2010 Valerio Proietti (http://mad4milk.net/) -License: Expat - Files: web/api/* Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat diff --git a/distros/ubuntu1604/source/lintian-overrides b/distros/ubuntu1604/source/lintian-overrides index 3669e5de8..f905a5a2f 100644 --- a/distros/ubuntu1604/source/lintian-overrides +++ b/distros/ubuntu1604/source/lintian-overrides @@ -1,9 +1,5 @@ -## Actually sources are there: "*-nc.js". -source-is-missing web/tools/mootools/mootools-*-yc.js - ## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-1.4.2.min.js +source-is-missing web/skins/*/js/jquery-3.5.1.min.js ## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/tools/mootools/mootools-*-yc.js -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-1.4.2.min.js +source-contains-prebuilt-javascript-object web/skins/*/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu1604/zoneminder.dirs b/distros/ubuntu1604/zoneminder.dirs index 6db3d5a95..3c7237bf3 100644 --- a/distros/ubuntu1604/zoneminder.dirs +++ b/distros/ubuntu1604/zoneminder.dirs @@ -5,5 +5,6 @@ var/cache/zoneminder/images var/cache/zoneminder/temp var/cache/zoneminder/cache usr/share/zoneminder/db +usr/share/zoneminder/fonts etc/zm/ etc/zm/conf.d diff --git a/distros/ubuntu1604/zoneminder.install b/distros/ubuntu1604/zoneminder.install index 67b135de5..17364c744 100644 --- a/distros/ubuntu1604/zoneminder.install +++ b/distros/ubuntu1604/zoneminder.install @@ -5,6 +5,7 @@ usr/lib/zoneminder usr/share/polkit-1 usr/share/zoneminder/db usr/share/zoneminder/www +usr/share/zoneminder/fonts # libzoneminder-perl files: usr/share/man/man3 diff --git a/distros/ubuntu1604/zoneminder.linktrees b/distros/ubuntu1604/zoneminder.linktrees index 2e843bbf1..61edb4173 100644 --- a/distros/ubuntu1604/zoneminder.linktrees +++ b/distros/ubuntu1604/zoneminder.linktrees @@ -1,14 +1,6 @@ ## cakephp #replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake -## libjs-mootools -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-nc.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-yc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-nc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-yc.js - ## libjs-jquery -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-1.4.2.min.js -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-1.4.2.min.js +#replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-3.5.1.min.js +#replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst index 7b7af708b..ae362a495 100644 --- a/distros/ubuntu1604/zoneminder.postinst +++ b/distros/ubuntu1604/zoneminder.postinst @@ -12,9 +12,7 @@ if [ "$1" = "configure" ]; then # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group chown www-data:root /var/log/zm chown www-data:www-data /var/lib/zm - if [ -z "$2" ]; then - chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* - fi + chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ] && [ "$(command -v a2enmod)" != "" ]; then echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." a2enmod cgi @@ -67,11 +65,19 @@ if [ "$1" = "configure" ]; then # This creates the user. echo "CREATE USER '${ZM_DB_USER}'@localhost IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql fi - echo "Updating permissions" - echo "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + echo "Updating permissions for user ${ZM_DB_USER}@localhost" + echo "GRANT LOCK TABLES,ALTER,DROP,SELECT,INSERT,UPDATE,DELETE,CREATE,INDEX,ALTER ROUTINE,CREATE ROUTINE, TRIGGER,EXECUTE,REFERENCES ON ${ZM_DB_NAME}.* TO '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - zmupdate.pl --nointeractive + zmupdate.pl -s --nointeractive + if [ $? -ne 0 ]; then + echo "Error updating db." + exit 1; + fi zmupdate.pl --nointeractive -f + if [ $? -ne 0 ]; then + echo "Error updating config." + exit 1; + fi # Add any new PTZ control configurations to the database (will not overwrite) zmcamtool.pl --import >/dev/null 2>&1 @@ -86,6 +92,8 @@ if [ "$1" = "configure" ]; then else echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." fi + + echo "Done Updating; starting ZoneMinder." deb-systemd-invoke restart zoneminder.service fi diff --git a/distros/ubuntu2004/TODO.Debian b/distros/ubuntu2004/TODO similarity index 100% rename from distros/ubuntu2004/TODO.Debian rename to distros/ubuntu2004/TODO diff --git a/distros/ubuntu2004/compat b/distros/ubuntu2004/compat index ec635144f..48082f72f 100644 --- a/distros/ubuntu2004/compat +++ b/distros/ubuntu2004/compat @@ -1 +1 @@ -9 +12 diff --git a/distros/ubuntu2004/conf/apache2/zoneminder.conf b/distros/ubuntu2004/conf/apache2/zoneminder.conf index e3164d36c..ae9f0dc3b 100644 --- a/distros/ubuntu2004/conf/apache2/zoneminder.conf +++ b/distros/ubuntu2004/conf/apache2/zoneminder.conf @@ -16,11 +16,6 @@ Alias /zm/cache /var/cache/zoneminder/cache # Apache 2.4 Require all granted - - # Apache 2.2 - Order deny,allow - Allow from all - Alias /zm /usr/share/zoneminder/www diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index 0a1f9e0de..01c3a8472 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -2,10 +2,8 @@ Source: zoneminder Section: net Priority: optional Maintainer: Isaac Connor -Uploaders: Isaac Connor -Build-Depends: debhelper, dh-systemd, sphinx-doc, python3-sphinx, dh-linktree, dh-systemd, dh-apache2 +Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 ,cmake - ,libx264-dev, libmp4v2-dev ,libavdevice-dev ,libavcodec-dev ,libavformat-dev @@ -17,6 +15,7 @@ Build-Depends: debhelper, dh-systemd, sphinx-doc, python3-sphinx, dh-linktree, d ,libbz2-dev ,libgcrypt20-dev ,libcurl4-gnutls-dev + ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat ,libpcre3-dev @@ -32,19 +31,15 @@ Build-Depends: debhelper, dh-systemd, sphinx-doc, python3-sphinx, dh-linktree, d ,libssl-dev ,libcrypt-eksblowfish-perl ,libdata-entropy-perl -# Unbundled (dh_linktree): - ,libjs-jquery - ,libjs-mootools -Standards-Version: 3.9.8 -Homepage: http://www.zoneminder.com/ -Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git -Vcs-Git: git://anonscm.debian.org/collab-maint/zoneminder.git + ,libvncserver-dev + ,libjwt-gnutls-dev +Standards-Version: 4.5.0 +Homepage: https://www.zoneminder.com/ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libmp4v2-2, libx264-155 ,libswscale5 ,libswresample3 ,ffmpeg @@ -71,8 +66,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libdata-uuid-perl ,libnumber-bytes-human-perl ,libfile-slurp-perl - ,mysql-client | mariadb-client | virtual-mysql-client - ,perl-modules + ,default-mysql-client | mariadb-client | virtual-mysql-client ,php-mysql, php-gd, php-apcu, php-apc | php-apcu-bc, php-json ,policykit-1 ,rsyslog | system-log-daemon @@ -80,11 +74,12 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libpcre3 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl + ,libvncclient1|libvncclient0 + ,libjwt-gnutls0 Recommends: ${misc:Recommends} ,libapache2-mod-php | php-fpm - ,mysql-server | mariadb-server | virtual-mysql-server + ,default-mysql-server | mariadb-server | virtual-mysql-server ,zoneminder-doc (>= ${source:Version}) - ,ffmpeg Suggests: fcgiwrap, logrotate Description: video camera security and surveillance solution ZoneMinder is intended for use in single or multi-camera video security @@ -148,7 +143,7 @@ Description: ZoneMinder documentation Package: zoneminder-dbg Section: debug -Priority: extra +Priority: optional Architecture: any Depends: zoneminder (= ${binary:Version}), ${misc:Depends} Description: Zoneminder -- debugging symbols diff --git a/distros/ubuntu2004/copyright b/distros/ubuntu2004/copyright index c48025a25..64189b0f4 100644 --- a/distros/ubuntu2004/copyright +++ b/distros/ubuntu2004/copyright @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ZoneMinder -Upstream-Contact: Philip Coombes +Upstream-Contact: Isaac Connor Source: https://github.com/ZoneMinder/ZoneMinder Comment: This package was originally debianized by matrix @@ -9,7 +9,6 @@ Comment: on Fri, 8 Dec 2006 10:19:43 +1100 Files-Excluded: web/skins/*/js/jquery-* - web/tools/mootools/*-yc.js Files: * Copyright: 2001-2014 Philip Coombes @@ -37,11 +36,6 @@ Comment: Includes Sizzle.js http://sizzlejs.com/ Released under the MIT, BSD, and GPL Licenses. -Files: web/tools/mootools/*.js -Copyright: 2009 Marcelo Jorge Vieira (metal) - 2006-2010 Valerio Proietti (http://mad4milk.net/) -License: Expat - Files: web/api/* Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat diff --git a/distros/ubuntu2004/rules b/distros/ubuntu2004/rules index 5a1cdb500..c137a9da2 100755 --- a/distros/ubuntu2004/rules +++ b/distros/ubuntu2004/rules @@ -1,5 +1,4 @@ #!/usr/bin/make -f -# -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 @@ -12,13 +11,14 @@ ARGS:= -DZM_NO_MMAP=ON endif %: - dh $@ --parallel --buildsystem=cmake --builddirectory=dbuild \ - --with systemd,sphinxdoc,apache2,linktree + dh $@ --buildsystem=cmake --builddirectory=dbuild \ + --with sphinxdoc,apache2,linktree override_dh_auto_configure: dh_auto_configure -- $(ARGS) \ -DCMAKE_VERBOSE_MAKEFILE=ON \ -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_MAN=0 \ -DZM_CONFIG_DIR="/etc/zm" \ -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ -DZM_RUNDIR="/run/zm" \ @@ -34,11 +34,26 @@ override_dh_clean: dh_clean $(MANPAGES1) $(RM) -r docs/_build -build-indep: - #$(MAKE) -C docs text +override_dh_auto_build-indep: $(MAKE) -C docs html + dh_auto_build + +MANPAGES1 = \ + dbuild/scripts/zmupdate.pl.1 \ + dbuild/scripts/zmaudit.pl.1 \ + dbuild/scripts/zmcamtool.pl.1 \ + dbuild/scripts/zmcontrol.pl.1 \ + dbuild/scripts/zmdc.pl.1 \ + dbuild/scripts/zmfilter.pl.1 \ + dbuild/scripts/zmpkg.pl.1 \ + dbuild/scripts/zmsystemctl.pl.1 \ + dbuild/scripts/zmtelemetry.pl.1 \ + dbuild/scripts/zmtrack.pl.1 \ + dbuild/scripts/zmtrigger.pl.1 \ + dbuild/scripts/zmvideo.pl.1 \ + dbuild/scripts/zmwatch.pl.1 \ + dbuild/scripts/zmx10.pl.1 -MANPAGES1 = dbuild/scripts/zmupdate.pl.1 $(MANPAGES1): # generate man page(s): pod2man -s1 --stderr --utf8 $(patsubst %.1, %, $@) $@ @@ -51,7 +66,7 @@ override_dh_installman: $(MANPAGES1) dh_installman --language=C $(MANPAGES1) override_dh_auto_install: - dh_auto_install --destdir=$(CURDIR)/debian/tmp + dh_auto_install --arch --destdir=$(CURDIR)/debian/tmp # remove worthless files: $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*/*/.packlist $(RM) -v $(CURDIR)/debian/tmp/usr/share/perl5/*/*.in @@ -67,11 +82,11 @@ override_dh_fixperms: chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf -override_dh_systemd_start: - dh_systemd_start --no-start +override_dh_installinit: + dh_installinit --no-start -override_dh_systemd_enable: - dh_systemd_enable --no-enable +override_dh_installsystemd: + dh_installsystemd --no-enable --no-start override_dh_apache2: dh_apache2 --noenable @@ -81,16 +96,3 @@ override_dh_strip: && dh_strip --dbg-package=zoneminder-dbg \ || dh_strip -#%: -# dh $@ --parallel --buildsystem=autoconf --with autoreconf -# -#override_dh_auto_configure: -# dh_auto_configure -- \ -# --sysconfdir=/etc/zm \ -# --with-mysql=/usr \ -# --with-webdir=/usr/share/zoneminder \ -# --with-ffmpeg=/usr \ -# --with-cgidir=/usr/lib/cgi-bin \ -# --with-webuser=www-data \ -# --with-webgroup=www-data \ -# --enable-mmap=yes diff --git a/distros/ubuntu2004/source/lintian-overrides b/distros/ubuntu2004/source/lintian-overrides index 3669e5de8..f905a5a2f 100644 --- a/distros/ubuntu2004/source/lintian-overrides +++ b/distros/ubuntu2004/source/lintian-overrides @@ -1,9 +1,5 @@ -## Actually sources are there: "*-nc.js". -source-is-missing web/tools/mootools/mootools-*-yc.js - ## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-1.4.2.min.js +source-is-missing web/skins/*/js/jquery-3.5.1.min.js ## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/tools/mootools/mootools-*-yc.js -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-1.4.2.min.js +source-contains-prebuilt-javascript-object web/skins/*/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu2004/zoneminder.dirs b/distros/ubuntu2004/zoneminder.dirs index 6db3d5a95..3c7237bf3 100644 --- a/distros/ubuntu2004/zoneminder.dirs +++ b/distros/ubuntu2004/zoneminder.dirs @@ -5,5 +5,6 @@ var/cache/zoneminder/images var/cache/zoneminder/temp var/cache/zoneminder/cache usr/share/zoneminder/db +usr/share/zoneminder/fonts etc/zm/ etc/zm/conf.d diff --git a/distros/ubuntu2004/zoneminder.init b/distros/ubuntu2004/zoneminder.init new file mode 100644 index 000000000..6132481f3 --- /dev/null +++ b/distros/ubuntu2004/zoneminder.init @@ -0,0 +1,91 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: zoneminder +# Required-Start: $network $remote_fs $syslog +# Required-Stop: $network $remote_fs $syslog +# Should-Start: mysql +# Should-Stop: mysql +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Control ZoneMinder as a Service +# Description: ZoneMinder CCTV recording and surveillance system +### END INIT INFO +# chkconfig: 2345 20 20 + +# Source function library. +. /lib/lsb/init-functions + +prog=ZoneMinder +ZM_PATH_BIN="/usr/bin" +RUNDIR="/run/zm" +TMPDIR="/tmp/zm" +command="$ZM_PATH_BIN/zmpkg.pl" + +start() { + echo -n "Starting $prog: " + export TZ=:/etc/localtime + mkdir -p "$RUNDIR" && chown www-data:www-data "$RUNDIR" + mkdir -p "$TMPDIR" && chown www-data:www-data "$TMPDIR" + $command start + RETVAL=$? + [ $RETVAL = 0 ] && echo success + [ $RETVAL != 0 ] && echo failure + echo + [ $RETVAL = 0 ] && touch /var/lock/zm + return $RETVAL +} +stop() { + echo -n "Stopping $prog: " + # + # Why is this status check being done? + # as $command stop returns 1 if zoneminder + # is stopped, which will result in + # this returning 1, which will stuff + # dpkg when it tries to stop zoneminder before + # uninstalling . . . + # + result=`$command status` + if [ ! "$result" = "running" ]; then + echo "Zoneminder already stopped" + echo + RETVAL=0 + else + $command stop + RETVAL=$? + [ $RETVAL = 0 ] && echo success + [ $RETVAL != 0 ] && echo failure + echo + [ $RETVAL = 0 ] && rm -f /var/lock/zm + fi +} +status() { + result=`$command status` + if [ "$result" = "running" ]; then + echo "ZoneMinder is running" + RETVAL=0 + else + echo "ZoneMinder is stopped" + RETVAL=1 + fi +} + +case "$1" in +'start') + start + ;; +'stop') + stop + ;; +'restart' | 'force-reload') + stop + start + ;; +'status') + status + ;; +*) + echo "Usage: $0 { start | stop | restart | status }" + RETVAL=1 + ;; +esac +exit $RETVAL diff --git a/distros/ubuntu2004/zoneminder.install b/distros/ubuntu2004/zoneminder.install index 67b135de5..aa672d29b 100644 --- a/distros/ubuntu2004/zoneminder.install +++ b/distros/ubuntu2004/zoneminder.install @@ -5,7 +5,10 @@ usr/lib/zoneminder usr/share/polkit-1 usr/share/zoneminder/db usr/share/zoneminder/www +usr/share/zoneminder/fonts +usr/share/zoneminder/icons +usr/share/applications/* # libzoneminder-perl files: -usr/share/man/man3 +#usr/share/man/man3 usr/share/perl5 diff --git a/distros/ubuntu2004/zoneminder.linktrees b/distros/ubuntu2004/zoneminder.linktrees deleted file mode 100644 index 2e843bbf1..000000000 --- a/distros/ubuntu2004/zoneminder.linktrees +++ /dev/null @@ -1,14 +0,0 @@ -## cakephp -#replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake - -## libjs-mootools -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-nc.js -replace /usr/share/javascript/mootools/mootools.js /usr/share/zoneminder/www/tools/mootools/mootools-core-1.3.2-yc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-nc.js -replace /usr/share/javascript/mootools/mootools-more.js /usr/share/zoneminder/www/tools/mootools/mootools-more-1.3.2.1-yc.js - -## libjs-jquery -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/classic/js/jquery-1.4.2.min.js -replace /usr/share/javascript/jquery/jquery.min.js /usr/share/zoneminder/www/skins/flat/js/jquery-1.4.2.min.js diff --git a/distros/ubuntu2004/zoneminder.postinst b/distros/ubuntu2004/zoneminder.postinst index 5d5ddd6ff..4be136a71 100644 --- a/distros/ubuntu2004/zoneminder.postinst +++ b/distros/ubuntu2004/zoneminder.postinst @@ -2,34 +2,88 @@ set +e +create_db () { + echo "Checking for db" + mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + # test if database if already present... + if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then + echo "Creating zm db" + cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf + if [ $? -ne 0 ]; then + echo "Error creating db." + exit 1; + fi + else + echo "Db exists." + fi +} + +create_update_user () { + USER_EXISTS="$(mysql --defaults-file=/etc/mysql/debian.cnf -sse "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '$ZM_DB_USER')")" + if [ $USER_EXISTS -ne 1 ]; then + echo "Creating zm user $ZM_DB_USER" + # This creates the user. + echo "CREATE USER '${ZM_DB_USER}'@${ZM_DB_HOST} IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql + fi + echo "Updating permissions for user ${ZM_DB_USER}@${ZM_DB_HOST}" + echo "GRANT LOCK tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine,trigger,execute,REFERENCES ON ${ZM_DB_NAME}.* TO '${ZM_DB_USER}'@${ZM_DB_HOST};" | mysql --defaults-file=/etc/mysql/debian.cnf mysql +} + +update_db () { + + zmupdate.pl -s --nointeractive + if [ $? -ne 0 ]; then + echo "Error updating db." + exit 1; + fi + zmupdate.pl --nointeractive -f + if [ $? -ne 0 ]; then + echo "Error updating config." + exit 1; + fi + + # Add any new PTZ control configurations to the database (will not overwrite) + zmcamtool.pl --import >/dev/null 2>&1 + echo "Done Updating" +} + if [ "$1" = "configure" ]; then - + . /etc/zm/zm.conf for CONFFILE in /etc/zm/conf.d/*.conf; do . "$CONFFILE" done - + # The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group chown www-data:root /var/log/zm chown www-data:www-data /var/lib/zm - if [ -z "$2" ]; then - chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* - fi + chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ] && [ "$(command -v a2enmod)" != "" ]; then echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." - a2enmod cgi + if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then + . /usr/share/apache2/apache2-maintscript-helper + apache2_invoke enmod cgi || exit $? + fi + fi + + SYSTEMD=0 + if [ -e "/run/systemd/system" ]; then + SYSTEMD=1 + echo "detected systemd" + # Ensure zoneminder is stopped + deb-systemd-invoke stop zoneminder.service || exit $? + else + # Ensure zoneminder is stopped + invoke-rc.d zoneminder stop || true fi if [ "$ZM_DB_HOST" = "localhost" ]; then - if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ] || [ -e "/etc/init.d/mysql" ]; then - # Ensure zoneminder is stopped - deb-systemd-invoke stop zoneminder.service || exit $? - + if [ $SYSTEMD -eq 1 ] && ([ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ]); then # # Get mysql started if it isn't running # - + if [ -e "/lib/systemd/system/mariadb.service" ]; then DBSERVICE="mariadb.service" else @@ -42,48 +96,50 @@ if [ "$1" = "configure" ]; then echo "run sudo systemctl restart $DBSERVICE then run sudo dpkg-reconfigure zoneminder." exit 1 fi - + if ! systemctl is-active --quiet mysql.service mariadb.service; then # Due to /etc/init.d service autogeneration, mysql.service always returns the status of mariadb.service # However, mariadb.service will not return the status of mysql.service. deb-systemd-invoke start $DBSERVICE fi - + # Make sure systemctl status exit code is 0; i.e. the DB is running if systemctl is-active --quiet "$DBSERVICE"; then - mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload - # test if database if already present... - if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then - echo "Creating zm db" - cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf - if [ $? -ne 0 ]; then - echo "Error creating db." - exit 1; - fi - # This creates the user. - echo "CREATE USER '${ZM_DB_USER}'@${ZM_DB_HOST} IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - fi - echo "Updating permissions" - echo "GRANT LOCK tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine,trigger,execute ON ${ZM_DB_NAME}.* TO '${ZM_DB_USER}'@${ZM_DB_HOST};" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - - zmupdate.pl --nointeractive - zmupdate.pl --nointeractive -f - - # Add any new PTZ control configurations to the database (will not overwrite) - zmcamtool.pl --import >/dev/null 2>&1 - echo "Done Updating; starting ZoneMinder." + create_db + create_update_user + update_db else echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' fi + + elif [ -e "/etc/init.d/mysql" ]; then + # + # Get mysql started if it isn't + # + if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then + service mysql start + fi + if $(/etc/init.d/mysql status >/dev/null 2>&1); then + create_db + create_update_user + update_db + else + echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' + fi + else echo 'MySQL/MariaDB not found; assuming remote server.' fi - - else - echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." fi - deb-systemd-invoke restart zoneminder.service +else + echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." +fi + +if [ $SYSTEMD -eq 1 ]; then + deb-systemd-invoke restart zoneminder.service +#else + #service zoneminder start || true fi #DEBHELPER# diff --git a/distros/ubuntu2004/zoneminder.tmpfile b/distros/ubuntu2004/zoneminder.tmpfiles similarity index 100% rename from distros/ubuntu2004/zoneminder.tmpfile rename to distros/ubuntu2004/zoneminder.tmpfiles diff --git a/docs/conf.py b/docs/conf.py index 9de7bd820..37ccd9d68 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,9 +15,6 @@ import sys import os -def setup(app): - app.add_stylesheet('zmstyle.css') - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -134,7 +131,9 @@ html_theme = 'default' # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] -#html_style='zmstyles.css' + +html_css_files = [ 'zmstyle.css' ] + # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/docs/faq.rst b/docs/faq.rst index 56b67d082..1db30e05f 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -27,7 +27,7 @@ After you've done that, you changes will automatically be loaded into zmfilter w Check the ``zmfilter.log`` file to make sure it is running as sometimes missing perl modules mean that it never runs but people don't always realize. **Purge By Age** -To delete events that are older than 7 days, create a new filter with "Date" set to "less than" and a value of "-7 days", sort by "date/time" in "asc"ending order, then enable the checkbox "delete all matches". You can also use a value of week or week and days: "-2 week" or "-2 week 4 day" +To delete events that are older than 7 days, create a new filter with "End Date" set to "less than" and a value of "-7 days", sort by "date/time" in "asc"ending order, then enable the checkbox "delete all matches". You can also use a value of week or week and days: "-2 week" or "-2 week 4 day" Save with 'Run Filter In Background' enabled to have it run automatically. Optional skip archived events: click on the plus sign next to -7 days to add another condition. "and" "archive status" equal to "unarchived only". @@ -534,4 +534,4 @@ The GPL license allows you produce systems based on GPL software provided your s I am having issues with zmNinja and/or Event Notification Server ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -zmNinja and the Event Notification Server are 3rd party solutions. The developer maintains exhaustive `documentation and FAQs `__. Please direct your questions there. \ No newline at end of file +zmNinja and the Event Notification Server are 3rd party solutions. The developer maintains exhaustive `documentation and FAQs `__. Please direct your questions there. diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index d09423bd9..2244177e6 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -60,7 +60,8 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file You can do this using: -:: +.. code-block:: + echo "deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS. diff --git a/docs/installationguide/multiserver.rst b/docs/installationguide/multiserver.rst index 14e52d29f..ce1cd26e6 100644 --- a/docs/installationguide/multiserver.rst +++ b/docs/installationguide/multiserver.rst @@ -21,6 +21,10 @@ New installs 1. Follow the normal instructions for your distro for installing ZoneMinder onto all the ZoneMinder servers in the normal fashion. Only a single database will be needed either as standalone, or on one of the ZoneMinder Servers. +.. sidebar :: Note + + For systemd based linux distros, inspect the zoneminder service file, typically found under /lib/systemd/system. Changes may be required for multiserver to function correctly. For example, the service file may check for a running instance of mysql or mariadb running locally on the server. This check will need to be removed. Rather than edit the service file directly, copy the service file to /etc/systemd/system and edit the file in that location. + 2. On each ZoneMinder server, edit zm.conf. Find the ZM_DB_HOST variable and set it to the name or ip address of your Database Server. Find the ZM_SERVER_HOST and enter a name for this ZoneMinder server. Use a name easily recognizable by you. This name is not used by ZoneMinder for dns or any other form of network conectivity. 3. Copy the file /usr/share/zoneminder/db/zm_create.sql from one of the ZoneMinder Servers to the machine targeted as the Database Server. @@ -46,7 +50,9 @@ Note that these commands are just an example and might not be secure enough for 9. From each ZoneMinder Server, mount the shared events folder on the Storage Server to the events folder on the local ZoneMinder Server. -NOTE: The location of this folder varies by distro. This folder is often found under "/var/lib/zoneminder/events" for RedHat based distros and "/var/cache/zoneminder/events" for Debain based distros. This folder is NOT a Symbolic Link! +.. sidebar :: Note + + The location of the ZoneMinder events folder varies by distro. This folder is often found under "/var/lib/zoneminder/events" for RedHat based distros and "/var/cache/zoneminder/events" for Debain based distros. This folder is NOT a Symbolic Link! 10. Open your browser and point it to the web console on any of the ZoneMinder Servers (they will all be the same). Open Options, click the Servers tab,and populate this screen with all of your ZoneMinder Servers. Each server has a field for its name and its hostname. The name is what you used for ZM_SERVER_HOST in step 2. The hostname is the network name or ip address ZoneMinder should use. diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index c18a3412a..b19080865 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -45,15 +45,47 @@ The following notes are based on real problems which have occurred by those who How to Install ZoneMinder ------------------------- -ZoneMinder releases are now being hosted at RPM Fusion. New users should navigate the `RPM Fusion site `__ then follow the instructions to enable that repo. RHEL/CentOS users must also navaigate to the `EPEL Site `_ and enable that repo as well. Once enabled, install ZoneMinder from the commandline: +ZoneMinder releases are hosted at RPM Fusion. New users should navigate to the `RPM Fusion site `__ then follow the instructions to enable that repo. + +.. sidebar :: Note + + RHEL/CentOS 7 users should use *yum* instead of *dnf* + +RHEL/CentOS 7 & 8 users must enable the EPEL repo: :: - sudo dnf install zoneminder + sudo dnf install epel-release + +RHEL/CentOS 8 users must also enable the PowerTools repo: -Note that RHEL/CentOS 7 users should use yum instead of dnf. +:: -Once ZoneMinder has been installed, it is critically important that you read the README file under /usr/share/doc/zoneminder-common. ZoneMinder will not run without completing the steps outlined in the README. + sudo dnf install dnf-plugins-core + sudo dnf config-manager --set-enabled PowerTools + +Once the additional repos are enabled, install ZoneMinder from the commandline. Choose the package that matches the desired web server. + +Install ZoneMinder for Apache web server: + +.. sidebar :: Note + + A virtual package called zoneminder exists. This package contains no files and will pull in the zoneminder-httpd package for backwards compatiblity. + +:: + + sudo dnf install zoneminder-httpd + +Install ZoneMinder for Nginx web server: + +:: + + sudo dnf install zoneminder-nginx + + +Once ZoneMinder has been installed, you must read the README file to complete the installation. Fedora users can find the README under /usr/share/doc/zoneminder-common. RHEL/CentOS users can find the README under /usr/share/doc/zoneminder-common-x.xx where x.xx is the version of zoneminder. + +ZoneMinder will *NOT* run without completing the steps shown in the README! How to Install Nightly Development Builds ----------------------------------------- @@ -62,21 +94,6 @@ ZoneMinder development packages, which represent the most recent build from our The feedback we get from those who use these development packages is extremely helpful. However, please understand these packages are intended for testing the latest master branch only. They are not intended to be used on any production system. There will be new bugs, and new features may not be documented. This is bleeding edge, and there might be breakage. Please keep that in mind when using this repo. We know from our user forum that this can't be stated enough. -How to Change from Zmrepo to RPM Fusion ---------------------------------------- - -As mentioned above, the place to get the latest ZoneMinder release is now `RPM Fusion `__. If you are currently using ZoneMinder release packages from Zmrepo, then the following steps will change you over to RPM Fusion: - -- Navigate to the `RPM Fusion site `__ and enable RPM Fusion on your system -- Now issue the following from the command line: - -:: - - sudo dnf remove zmrepo - sudo dnf update - -Note that RHEL/CentOS 7 users should use yum instead of dnf. - How to Build Your Own ZoneMinder Package ------------------------------------------ diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 7b714e51a..7f2a01eb2 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -18,7 +18,6 @@ achieve the same result by running: sudo apt-get install tasksel sudo tasksel install lamp-server -During installation it will ask you to set up a master/root password for the MySQL. Installing LAMP is not ZoneMinder specific so you will find plenty of resources to guide you with a quick search. @@ -57,7 +56,7 @@ Update repo and upgrade. .. sidebar :: Note - The MySQL default configuration file (/etc/mysql/mysql.cnf)is read through + The MySQL default configuration file (/etc/mysql/mysql.cnf) is read through several symbolic links beginning with /etc/mysql/my.cnf as follows: | /etc/mysql/my.cnf -> /etc/alternatives/my.cnf @@ -66,7 +65,7 @@ Update repo and upgrade. Certain new defaults in MySQL 5.7 cause some issues with ZoneMinder < 1.32.0, the workaround is to modify the sql_mode setting of MySQL. Please note that these -changes are NOT required for ZoneMinder 1.32.0 and some people have reported them +changes are NOT required for ZoneMinder 1.32+ and some people have reported them causing problems in 1.32.0. To better manage the MySQL server it is recommended to copy the sample config file and @@ -113,7 +112,7 @@ This step should not be required on ZoneMinder 1.32.0. :: mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on zm.* to 'zmuser'@localhost identified by 'zmpass';" + mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute,references on zm.* to 'zmuser'@localhost identified by 'zmpass';" **Step 6:** Set permissions @@ -148,23 +147,6 @@ You may also want to enable to following modules to improve caching performance systemctl enable zoneminder systemctl start zoneminder -**Step 9:** Edit Timezone in PHP - -:: - - nano /etc/php/7.2/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - CTRL+o then [Enter] to save CTRL+x to exit @@ -186,8 +168,8 @@ CTRL+x to exit :: { - "version": "1.29.0", - "apiversion": "1.29.0.1" + "version": "1.34.0", + "apiversion": "1.34.0.1" } **Congratulations** Your installation is complete @@ -339,27 +321,6 @@ You may also want to enable to following modules to improve caching performance systemctl enable zoneminder systemctl start zoneminder -**Step 9:** Edit Timezone in PHP - -:: - - nano /etc/php/7.0/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - **Step 10:** Reload Apache service :: @@ -377,102 +338,14 @@ CTRL+x to exit :: { - "version": "1.29.0", - "apiversion": "1.29.0.1" + "version": "1.34.0", + "apiversion": "1.34.0.1" } **Congratulations** Your installation is complete PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ -Easy Way: Ubuntu 14.x (Trusty) ------------------------------- -**These instructions are for a brand new ubuntu 14.x system which does not have ZM installed.** - -**Step 1:** Either run commands in this install using sudo or use the below to become root - -:: - - sudo -i - -**Step 2:** Install ZoneMinder - -:: - - add-apt-repository ppa:iconnor/zoneminder - apt-get update - apt-get install zoneminder - -(just press OK for the prompts you get) - -**Step 3:** Set up DB - -:: - - mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" - -**Step 4:** Set up Apache - -:: - - a2enconf zoneminder - a2enmod rewrite - a2enmod cgi - -**Step 5:** Make zm.conf readable by web user. - -:: - - sudo chown www-data:www-data /etc/zm/zm.conf - - -**Step 6:** Edit Timezone in PHP - -:: - - nano /etc/php5/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone, see [this](https://www.php.net/manual/en/timezones.php). -**Don't forget to remove the ; from in front of date.timezone** - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - -**Step 7:** Restart Apache service and start ZoneMinder - -:: - - service apache2 reload - service zoneminder start - - -**Step 8:** Making sure ZoneMinder works - -1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console - -2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` - - If it is working correctly you should get version information similar to the example below: - - :: - - { - "version": "1.29.0", - "apiversion": "1.29.0.1" - } - -**Congratulations** Your installation is complete - Harder Way: Build Package From Source ------------------------------------- (These instructions assume installation from source on a ubuntu 15.x+ system) @@ -509,7 +382,7 @@ To build the latest stable release: Note that the distribution will be guessed using ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` -which simply extracts your distribution name - like "vivid", "trusty" etc. You +which simply extracts your distribution name - like "xenial", "bionic" etc. You can always specify it using --distro=your distro name if you know it. As far as the script goes, it checks if your distro is "trusty" in which case it pulls in pre-systemd release configurations and if its not "trusty" it assumes its based on systemd @@ -528,7 +401,7 @@ This should now create a bunch of .deb files :: sudo gdebi zoneminder__.deb - (example sudo gdebi zoneminder_1.29.0-vivid-2016012001_amd64.deb) + (example sudo gdebi zoneminder_1.34.0-bionic-2021020801_amd64.deb) **This will report DB errors - ignore - you need to configure the DB and some other stuff** diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index 436034da0..ae1b4b132 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -70,13 +70,17 @@ Here is what the filter window looks like * %EPS% Path to the event stream * %EPF1% Path to the frame view for the first alarmed event image * %EPFM% Path to the frame view for the (first) event image with the highest score - * %EFMOD% Path to image containing object detection, in frame view + * %EPFMOD% Path to image containing object detection, in frame view + * %EPFMODG% Path to image containing object detection animated gif version, in frame view * %EPI% Path to the event images * %EPI1% Path to the first alarmed event image, suitable for use in img tags * %EPIM% Path to the (first) event image with the highest score, suitable for use in img tags - * %EIMOD% Path to image containing object detection, suitable for use in img tags + * %EPIMOD% Path to image containing object detection, suitable for use in img tags + * %EPIMODG% Path to image containing object detection animated gif version, suitable for use in img tags * %EI1% Attach first alarmed event image * %EIM% Attach (first) event image with the highest score + * %EIMOD% Attach image containing object detection + * %EIMODG% Attach image containing object detection animated gif version * %EV% Attach event mpeg video * %MN% Name of the monitor * %MET% Total number of events for the monitor diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 0b40d7fb9..99f2ea4ba 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -51,7 +51,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of * **A**: The options menu lets you configure many aspects of ZoneMinder. Refer to :doc:`options`. * **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`. -* **C**: ZoneMinder allows you to group monitors gor logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. +* **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. * **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`. * **E**: The Cycle option allows you to rotate between live views of each cofigured monitor. * **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around. diff --git a/fonts/CMakeLists.txt b/fonts/CMakeLists.txt new file mode 100644 index 000000000..0d54e8174 --- /dev/null +++ b/fonts/CMakeLists.txt @@ -0,0 +1,4 @@ +# Glob all database upgrade scripts +file(GLOB fontfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.zmfnt") +# Install the fonts +install(FILES ${fontfileslist} DESTINATION "${ZM_FONTDIR}") diff --git a/fonts/default.zmfnt b/fonts/default.zmfnt new file mode 100644 index 000000000..e13998a39 Binary files /dev/null and b/fonts/default.zmfnt differ diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index f794241a8..e51636771 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -17,7 +17,8 @@ configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY) # Install Policykit rules and actions into the proper folders only on systems with systemd if(WITH_SYSTEMD) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") -endif(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") +endif() + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications") diff --git a/onvif/modules/CMakeLists.txt b/onvif/modules/CMakeLists.txt index d7ddbf466..502970af9 100644 --- a/onvif/modules/CMakeLists.txt +++ b/onvif/modules/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # MAKEMAKER_NOECHO_COMMAND previously defined in /scripts/zoneminder/CMakeLists.txt # Add build target for the perl modules -add_custom_target(zmonvifmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") +add_custom_target(zmonvifmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/onvif/modules/lib/ONVIF/Client.pm b/onvif/modules/lib/ONVIF/Client.pm index c7ce21092..738621ded 100644 --- a/onvif/modules/lib/ONVIF/Client.pm +++ b/onvif/modules/lib/ONVIF/Client.pm @@ -123,14 +123,19 @@ sub get_service_urls { ); if ( $result ) { print "Have results from GetServices\n" if $verbose; - foreach my $svc ( @{ $result->get_Service() } ) { - my $short_name = $namespace_map{$svc->get_Namespace()}; - my $url_svc = $svc->get_XAddr()->get_value(); - if ( defined $short_name && defined $url_svc ) { - print "Got $short_name service $url_svc\n" if $verbose; - $self->set_service($short_name, 'url', $url_svc); - } - } + my $services = $result->get_Service(); + if ( $services ) { + foreach my $svc ( @{ $services } ) { + my $short_name = $namespace_map{$svc->get_Namespace()}; + my $url_svc = $svc->get_XAddr()->get_value(); + if ( defined $short_name && defined $url_svc ) { + print "Got $short_name service $url_svc\n" if $verbose; + $self->set_service($short_name, 'url', $url_svc); + } + } # end foreach service + } else { + print "No services from GetServices\n" if $verbose; + } # end if services } else { print "No results from GetServices\n" if $verbose; } diff --git a/onvif/proxy/CMakeLists.txt b/onvif/proxy/CMakeLists.txt index 1a40c0ffb..779b4d47e 100644 --- a/onvif/proxy/CMakeLists.txt +++ b/onvif/proxy/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # MAKEMAKER_NOECHO_COMMAND previously defined in /scripts/zoneminder/CMakeLists.txt # Add build target for the perl modules -add_custom_target(zmonvifproxy ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") +add_custom_target(zmonvifproxy ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/output ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl ONVIF proxy module") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 5502a42a8..79df1f03a 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -22,22 +22,24 @@ configure_file(zmcamtool.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" @ONLY) configure_file(zmsystemctl.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" @ONLY) configure_file(zmtelemetry.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" @ONLY) if(NOT ZM_NO_X10) - configure_file(zmx10.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" @ONLY) -endif(NOT ZM_NO_X10) + configure_file(zmx10.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" @ONLY) +endif() #configure_file(zmdbbackup.in zmdbbackup @ONLY) #configure_file(zmdbrestore.in zmdbrestore @ONLY) configure_file(zm.in "${CMAKE_CURRENT_BINARY_DIR}/zm" @ONLY) #configure_file(zmeventdump.in zmeventdump @ONLY) # Generate man files for the perl scripts destined for the bin folder -file(GLOB perlscripts "${CMAKE_CURRENT_BINARY_DIR}/*.pl") -FOREACH(PERLSCRIPT ${perlscripts}) +if(BUILD_MAN) + file(GLOB perlscripts "${CMAKE_CURRENT_BINARY_DIR}/*.pl") + foreach(PERLSCRIPT ${perlscripts}) get_filename_component(PERLSCRIPTNAME ${PERLSCRIPT} NAME) POD2MAN(${PERLSCRIPT} ${PERLSCRIPTNAME} 8 ${ZM_MANPAGE_DEST_PREFIX}) -ENDFOREACH(PERLSCRIPT ${perlscripts}) + endforeach(PERLSCRIPT ${perlscripts}) +endif() # Install the perl scripts -install(FILES +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @@ -56,9 +58,9 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) if(NOT ZM_NO_X10) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -endif(NOT ZM_NO_X10) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() if(WITH_SYSTEMD) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -endif(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() diff --git a/scripts/ZoneMinder/CMakeLists.txt b/scripts/ZoneMinder/CMakeLists.txt index 876a33da3..2a8708d2f 100644 --- a/scripts/ZoneMinder/CMakeLists.txt +++ b/scripts/ZoneMinder/CMakeLists.txt @@ -2,14 +2,14 @@ # If this is an out-of-source build, copy the files we need to the binary directory if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Changes" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/META.yml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/README" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/t" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Changes" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/Makefile.PL" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/META.yml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/README" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/t" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/lib" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE) +endif() # Create files from the .in files configure_file(lib/ZoneMinder/Base.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/Base.pm" @ONLY) @@ -19,13 +19,13 @@ configure_file(lib/ZoneMinder/ConfigData.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ configure_file(lib/ZoneMinder/ONVIF.pm.in "${CMAKE_CURRENT_BINARY_DIR}/lib/ZoneMinder/ONVIF.pm" @ONLY) if(CMAKE_VERBOSE_MAKEFILE) - set(MAKEMAKER_NOECHO_COMMAND "") -else(CMAKE_VERBOSE_MAKEFILE) - set(MAKEMAKER_NOECHO_COMMAND "NOECHO=\"1>/dev/null\"") -endif(CMAKE_VERBOSE_MAKEFILE) + set(MAKEMAKER_NOECHO_COMMAND "") +else() + set(MAKEMAKER_NOECHO_COMMAND "NOECHO=\"1>/dev/null\"") +endif() # Add build target for the perl modules -add_custom_target(zmperlmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl modules") +add_custom_target(zmperlmodules ALL perl Makefile.PL ${ZM_PERL_MM_PARMS_FULL} FIRST_MAKEFILE=MakefilePerl DESTDIR="${CMAKE_CURRENT_BINARY_DIR}/output" ${MAKEMAKER_NOECHO_COMMAND} COMMAND make -f MakefilePerl pure_install COMMENT "Building ZoneMinder perl modules") # Add install target for the perl modules install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/output/" DESTINATION "/") diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index cf2b59c85..a1d9521f6 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Config Data Module, $Date: 2011-01-20 18:49:42 +0000 (Thu, 20 Jan 2011) $, $Revision: 3230 $ +# ZoneMinder Config Data Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -479,9 +479,36 @@ our @options = ( type => $types{string}, category => 'system', }, + { + name => 'ZM_OPT_USE_GEOLOCATION', + description => 'Add geolocation features to ZoneMinder.', + help => 'Whether or not to enable Latitude/Longitude settings on Monitors and enable mapping options.', + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_GEOLOCATION_TILE_PROVIDER', + description => 'Tile provider to use for maps.', + help => 'OpenStreetMaps does not itself provide the images to use in the map. There are many to choose from. Mapbox.com is one example that offers free tiles and has been tested during development of this feature.', + requires => [ + {name=>'ZM_OPT_USE_GEOLOCATION', value=>'yes'} + ], + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_OPT_GEOLOCATION_ACCESS_TOKEN', + description => 'Access Token for the tile provider used for maps.', + help => 'OpenStreetMaps does not itself provide the images to use in the map. There are many to choose from. Mapbox.com is one example that offers free tiles and has been tested during development of this feature. You must go to mapbox.com and sign up and get an access token and cutnpaste it here.', + requires => [ + {name=>'ZM_OPT_USE_GEOLOCATION', value=>'yes'} + ], + type => $types{string}, + category => 'system', + }, { name => 'ZM_SYSTEM_SHUTDOWN', - default => 'true', + default => 'yes', description => 'Allow Admin users to power off or restart the system from the ZoneMinder UI.', help => 'The system will need to have sudo installed and the following added to /etc/sudoers~~ ~~ @@ -491,6 +518,14 @@ our @options = ( type => $types{boolean}, category => 'system', }, + { + name => 'ZM_FEATURES_SNAPSHOTS', + default => 'no', + description => 'Enable snapshot functionality.', + help => 'Snapshots are a collection of events. They can be created using the snapshot button in montage view. All visible monitors will have a short event created, archived and added to the Snapshot.', + type => $types{boolean}, + category => 'hidden', + }, { name => 'ZM_USE_DEEP_STORAGE', default => 'yes', @@ -920,6 +955,19 @@ our @options = ( type => $types{integer}, category => 'network', }, + { + name => 'ZM_MIN_RTSP_PORT', + default => '', + description => 'Start of port range to contact for RTSP streaming video.', + help => q` + The beginng of a port range that will be used to offer + RTSP streaming of live captured video. + Each monitor will use this value plus the Monitor Id to stream + content. So a value of 2000 here will cause a stream for Monitor 1 to + hit port 2001.`, + type => $types{integer}, + category => 'network', + }, { name => 'ZM_MIN_RTP_PORT', default => '40200', @@ -2621,7 +2669,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_SORT_FIELD', - default => 'StartDateTime', + default => 'StartTime', description => 'Default field the event lists are sorted by', help => q` Events in lists can be initially ordered in any way you want. @@ -2633,7 +2681,7 @@ our @options = ( `, type => { db_type =>'string', - hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartDateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, @@ -2721,6 +2769,19 @@ our @options = ( requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], category => 'web', }, + { + name => 'ZM_WEB_ANIMATE_THUMBS', + default => 'yes', + description => 'Enlarge and show the live stream when a thumbnail is hovered over', + help => q` + Enabling this option causes the static thumbnail, shown on certain + views, to enlarge and show the live stream, when the thumbnail is + hovered over by the mouse. + `, + type => $types{boolean}, + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', + }, { name => 'ZM_WEB_USE_OBJECT_TAGS', default => 'yes', @@ -3740,6 +3801,14 @@ our @options = ( type => $types{boolean}, category => 'logging', }, + { + name => 'ZM_FONT_FILE_LOCATION', + default => '@ZM_FONTDIR@/default.zmfnt', + description => 'Font file location', + help => 'This font is used for timestamp labels.', + type => $types{string}, + category => 'config', + }, ); our %options_hash = map { ( $_->{name}, $_ ) } @options; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index acce8eb4a..feb12a0ca 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -29,6 +29,7 @@ use strict; use warnings; require ZoneMinder::Base; +require ZoneMinder::Monitor; our $VERSION = $ZoneMinder::Base::VERSION; @@ -95,8 +96,8 @@ sub close { sub loadMonitor { my $self = shift; if ( !$self->{Monitor} ) { - if ( !($self->{Monitor} = zmDbGetMonitor($self->{id})) ) { - Fatal('Monitor id '.$self->{id}.' not found or not controllable'); + if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{id})) ) { + Fatal('Monitor id '.$self->{id}.' not found'); } if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { # Convert to microseconds. diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 59e4f7511..df3a538ea 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -75,7 +75,7 @@ sub open { } # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth - my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); + my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/magicBox.cgi?action=getDeviceType'); if ( $res->is_success ) { $self->{state} = 'open'; @@ -121,7 +121,7 @@ sub open { Debug('No headers line'); } # end if headers } else { - Error("Failed to get $$self{base_url}cgi-bin/ptz.cgi ".$res->status_line()); + Error("Failed to get $$self{base_url}cgi-bin/magicBox.cgi?action=getDeviceType ".$res->status_line()); } # end if $res->status_line() eq '401 Unauthorized' @@ -187,67 +187,65 @@ sub moveAbs ## Up, Down, Left, Right, etc. ??? Doesn't make sense here... sub moveConUp { my $self = shift; Debug('Move Up'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Up&channel=0&arg1=0&arg2=1&arg3=0'); - usleep(500); ##XXX Should this be passed in as a "speed" parameter? - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Up&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=Up&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConDown { my $self = shift; Debug('Move Down'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0'); - usleep(500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=Down&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConLeft { my $self = shift; Debug('Move Left'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0'); - usleep(500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=Left&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConRight { my $self = shift; Debug('Move Right'); - # $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=270&arg2=5&arg3=0' ); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Right&channel=0&arg1=0&arg2=1&arg3=0'); - usleep(500); - Debug('Move Right Stop'); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=Right&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConUpRight { my $self = shift; Debug('Move Diagonally Up Right'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0'); - usleep(500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConDownRight { my $self = shift; Debug('Move Diagonally Down Right'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0'); - usleep(500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{LastCmd} = 'code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'; + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConUpLeft { my $self = shift; Debug('Move Diagonally Up Left'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0'); - usleep(500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub moveConDownLeft { my $self = shift; Debug('Move Diagonally Down Left'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0'); - usleep (500); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } # Stop is not "correctly" implemented as control_functions.php translates this to "Center" @@ -256,8 +254,15 @@ sub moveConDownLeft { sub moveStop { my $self = shift; - Debug('Move Stop/Center'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1'); + if ($$self{LastCmd}) { + Debug('Move Stop '.$$self{LastCmd}); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&'.$$self{LastCmd}); + $$self{LastCmd} = ''; + $$self{Monitor}->resumeMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + } else { + Debug('Move Stop/Center'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1'); + } } # Move Camera to Home Position @@ -307,17 +312,17 @@ sub moveMap { sub zoomConTele { my $self = shift; Debug('Zoom continuous tele'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0'); - usleep(100000); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=ZoomTele&channel=0&arg1=0&arg2=0&arg3=0&arg4=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } sub zoomConWide { my $self = shift; Debug('Zoom continuous wide'); - $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0'); - usleep (100000); - $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0'); + $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; + $$self{LastCmd} = 'code=ZoomWide&channel=0&arg1=0&arg2=0&arg3=0&arg4=0'; + $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FoscamCGI.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FoscamCGI.pm new file mode 100644 index 000000000..57e44ccf8 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FoscamCGI.pm @@ -0,0 +1,354 @@ +# ========================================================================== +# +# ZoneMinder FoscamCGI Control Protocol Module +# Copyright (C) Jan M. Hochstein +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module actually contains an implementation of the Foscam CGI device control protocol +# +package ZoneMinder::Control::FoscamCGI; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our %CamParams = (); + +# ========================================================================== +# +# On ControlAddress use the format : +# USERNAME:PASSWORD@ADDRESS:PORT +# eg : admin:@10.1.2.1:80 +# zoneminder:zonepass@10.0.100.1:40000 +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +use Time::HiRes qw( usleep ); + +sub open { + my $self = shift; + + $self->loadMonitor(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + + $self->{state} = 'open'; +} + +sub sendCmd { + my $self = shift; + my $cmd = shift; + my $result = undef; + $self->printMsg($cmd, 'Tx'); + + my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) { + $result = !undef; + } else { + Error("Error check failed:'".$res->status_line()."'" ); + } + + return $result; +} + +sub getCamParams { + my $self = shift; + + my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/get_camera_params.cgi'); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) { + # Parse results setting values in %FCParams + my $content = $res->decoded_content; + + while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { + $CamParams{$1} = $2; + } + } else { + Error("Error check failed:'".$res->status_line()."'"); + } +} + +#autoStop +#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab +sub autoStop { + my $self = shift; + my $stop_command = shift; + my $autostop = shift; + if ( $stop_command && $autostop ) { + Debug('Auto Stop'); + usleep($autostop); + my $cmd = 'decoder_control.cgi?command='.$stop_command; + $self->sendCmd($cmd); + } +} + +# Reset the Camera +sub reset { + my $self = shift; + Debug('Camera Reset'); + my $cmd = 'reboot.cgi?'; + $self->sendCmd($cmd); +} + +#Up Arrow +sub moveConUp +{ + my $self = shift; + my $stop_command = "1"; + Debug( "Move Up" ); + my $cmd = "decoder_control.cgi?command=0"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Down Arrow +sub moveConDown +{ + my $self = shift; + my $stop_command = "3"; + Debug( "Move Down" ); + my $cmd = "decoder_control.cgi?command=2"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Left Arrow +sub moveConLeft +{ + my $self = shift; + my $stop_command = "5"; + Debug( "Move Left" ); + my $cmd = "decoder_control.cgi?command=4"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Right Arrow +sub moveConRight +{ + my $self = shift; + my $stop_command = "7"; + Debug( "Move Right" ); + my $cmd = "decoder_control.cgi?command=6"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Zoom In +sub zoomConTele +{ + my $self = shift; + my $stop_command = "17"; + Debug( "Zoom Tele" ); + my $cmd = "decoder_control.cgi?command=18"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Zoom Out +sub zoomConWide +{ + my $self = shift; + my $stop_command = "19"; + Debug( "Zoom Wide" ); + my $cmd = "decoder_control.cgi?command=16"; + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +#Diagonally Up Right Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->moveConUp( ); + $self->moveConRight( ); +} + +#Diagonally Down Right Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + $self->moveConDown( ); + $self->moveConRight( ); +} + +#Diagonally Up Left Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + $self->moveConUp( ); + $self->moveConLeft( ); +} + +#Diagonally Down Left Arrow +#This camera does not have builtin diagonal commands so we emulate them +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Down Left" ); + $self->moveConDown( ); + $self->moveConLeft( ); +} + +#Stop +sub moveStop +{ + my $self = shift; + Debug( "Move Stop" ); + my $cmd = "decoder_control.cgi?command=1"; + $self->sendCmd( $cmd ); +} + +#Set Camera Preset +#Presets must be translated into values internal to the camera +#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + + if (( $preset >= 1 ) && ( $preset <= 8 )) { + my $cmd = "decoder_control.cgi?command=".(($preset*2) + 28); + $self->sendCmd( $cmd ); + } +} + +#Recall Camera Preset +#Presets must be translated into values internal to the camera +#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + + if (( $preset >= 1 ) && ( $preset <= 8 )) { + my $cmd = "decoder_control.cgi?command=".(($preset*2) + 29); + $self->sendCmd( $cmd ); + } + + if ( $preset == 9 ) { + $self->horizontalPatrol(); + } + + if ( $preset == 10 ) { + $self->horizontalPatrolStop(); + } +} + +#Horizontal Patrol - Vertical Patrols are not supported +sub horizontalPatrol +{ + my $self = shift; + Debug( "Horizontal Patrol" ); + my $cmd = "decoder_control.cgi?command=20"; + $self->sendCmd( $cmd ); +} + +#Horizontal Patrol Stop +sub horizontalPatrolStop +{ + my $self = shift; + Debug( "Horizontal Patrol Stop" ); + my $cmd = "decoder_control.cgi?command=21"; + $self->sendCmd( $cmd ); +} + +# Increase Brightness +sub irisAbsOpen +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'brightness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'brightness'} += $step; + $CamParams{'brightness'} = 255 if ($CamParams{'brightness'} > 255); + Debug( "Iris $CamParams{'brightness'}" ); + my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; + $self->sendCmd( $cmd ); +} + +# Decrease Brightness +sub irisAbsClose +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'brightness'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'brightness'} -= $step; + $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); + Debug( "Iris $CamParams{'brightness'}" ); + my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; + $self->sendCmd( $cmd ); +} + +# Increase Contrast +sub whiteAbsIn +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'contrast'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'contrast'} += $step; + $CamParams{'contrast'} = 6 if ($CamParams{'contrast'} > 6); + Debug( "Iris $CamParams{'contrast'}" ); + my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; + $self->sendCmd( $cmd ); +} + +# Decrease Contrast +sub whiteAbsOut +{ + my $self = shift; + my $params = shift; + $self->getCamParams() unless($CamParams{'contrast'}); + my $step = $self->getParam( $params, 'step' ); + + $CamParams{'contrast'} -= $step; + $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); + Debug( "Iris $CamParams{'contrast'}" ); + my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; + $self->sendCmd( $cmd ); +} + +1; + diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index a62c756d8..8e5b2c9e3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -184,6 +184,7 @@ sub moveVector { # Send it to the camera $self->PutCmd($command,$xml); } +sub zoomStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); } sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm index ab7bc03ec..f10b7aabb 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm @@ -122,7 +122,17 @@ sub authentificationHeader { my $nonceBase64 = encode_base64($nonce, ''); my $currentDate = DateTime->now()->iso8601().'Z'; - return '' . $username . '' . digestBase64($nonce, $currentDate, $password) . '' . $nonceBase64 . '' . $currentDate . ''; + return ' + + + + ' . $username . ' + ' . digestBase64($nonce, $currentDate, $password) . ' + ' . $nonceBase64 . ' + ' . $currentDate . ' + + +'; } sub sendCmd { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index 84b3c8a14..8bdf1db42 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -3,6 +3,7 @@ package ZoneMinder::Control::Trendnet; use 5.006; use strict; use warnings; +use Time::HiRes qw(usleep); require ZoneMinder::Base; require ZoneMinder::Control; @@ -101,16 +102,22 @@ sub open { } # end sub open sub sendCmd { - # This routine is used for all moving, which are all GET commands... - my $self = shift; - my $cmd = shift; + if (!$self->{Monitor}->{ModectDuringPTZ}) { + $$self{Monitor}->suspendMotionDetection(); + } + + my $cmd = shift; my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd; my $res = $self->{ua}->get($url); - Debug('sendCmd command: ' . $url); + + if (!$self->{Monitor}->{ModectDuringPTZ}) { + usleep(10000); + $$self{Monitor}->resumeMotionDetection(); + } if ( $res->is_success ) { Debug($res->content); return !undef; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm index c830d28aa..12a78144c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm @@ -1,7 +1,7 @@ # ========================================================================== # -# ZoneMinder ONVIF Control Protocol Module -# Copyright (C) Jan M. Hochstein +# ZoneMinder ONVIF Control Protocol Module, $Date: 2021-02-25 22:07:00 +0000 (Thu, 25 Feb 2021) $, $Revision: 0001 $ +# Based on the Netcat onvif script by Andrew Bauer (knnniggett@users.sourceforge.net) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -19,28 +19,55 @@ # # ========================================================================== # -# This module contains the implementation of the ONVIF device control protocol +# This module contains the implementation of onvif protocol # package ZoneMinder::Control::onvif; use 5.006; use strict; use warnings; +use MIME::Base64; +use Digest::SHA; +use DateTime; +use URI; +use Data::Dumper; +require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our %CamParams = (); +our ($profileToken, $address, $port, %identity); +my ( $controlUri, $scheme ); + # ========================================================================== # -# ONVIF Control Protocol +# This script sends ONVIF compliant commands and may work with other cameras # -# On ControlAddress use the format : -# USERNAME:PASSWORD@ADDRESS:PORT -# eg : admin:@10.1.2.1:80 -# zoneminder:zonepass@10.0.100.1:40000 +# Configuration options (Source->Control tab) +# - Control Type: ONVIF +# - Control Device: prof0 - this is dependant on camera. It maybe required to sniff the traffic using Wireshark to find this out. If left empty value of "000" will be used. +# - Control Address: ://[:@][:port][control_uri] +# - Auto Stop Timeout: 1.00 - how long shold the camera move for when move command is issued. Value of 1.00 means 1s. +# - Track Motion: NOT IMPLEMENTED - this suppose to be a feature for automatic camera scrolling (moving). +# - Track Delay: NOT IMPLEMENTED +# - Return Location: Home|Preset 1 - NOT IMPLEMENTED +# +# Absolute minimum required supported "Control Address" would be: +# - 192.168.1.199 +# This will use the following defaults: +# - port: 80 +# - Control Device: 000 +# - Control URI: /onvif/PTZ +# - No authentication +# - No Auto Stop Timeout (on movement command the camera will keep moving until it reaches it's edge) +# +# Example Control Address values: +# - http://user:password@192.168.1.199:888/onvif/device_control :Connect to camera at IP: 192.168.1.199 on port 888 with "username" and "password" credentials using /onvif/device_control URI +# - user:password@192.168.1.199 :Connect to camera at IP: 192.168.1.199 on default port 80 with "username" and "password" credentials using default /onvif/PTZ URI +# - 192.168.1.199 :Connect to camera at IP: 192.168.1.199 without any authentication and use the default /onvif/PTZ URI over HTTP. # # ========================================================================== @@ -54,6 +81,11 @@ sub open { $self->loadMonitor(); + $profileToken = $self->{Monitor}->{ControlDevice}; + if ($profileToken eq '') { $profileToken = '000'; } + + parseControlAddress($self->{Monitor}->{ControlAddress}); + use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); @@ -61,39 +93,140 @@ sub open { $self->{state} = 'open'; } +sub parseControlAddress { + + my $controlAddress = shift; + + #make sure url start with a scheme + if ( $controlAddress !~ m'^https?://') { + $scheme = "http"; + $controlAddress = $scheme."://".$controlAddress; + } + + my $url = URI->new($controlAddress); + + #set the scheme + $scheme = $url->scheme; + + #If we have authinfo + if ($url->userinfo){ + my ($username , $password) = split /:/, $url->userinfo; + %identity = (username => $username, password => $password); + } + + #If we have no explicitly defined port + if (!$url->port){ + $port = $url->default_port; + } else { + $port = $url->port; + } + + if (!$url->path){ + $controlUri = "/onvif/PTZ"; + } else { + $controlUri = $url->path; + } + + $address = $url->host; +} + +sub digestBase64 { + my ($nonce, $date, $password) = @_; + my $shaGenerator = Digest::SHA->new(1); + $shaGenerator->add($nonce . $date . $password); + return encode_base64($shaGenerator->digest, ''); +} + +sub authentificationHeader { + my ($username, $password) = @_; + my @set = ('0' ..'9', 'A' .. 'Z', 'a' .. 'z'); + my $nonce = join '' => map $set[rand @set], 1 .. 20; + + my $nonceBase64 = encode_base64($nonce, ''); + my $currentDate = DateTime->now()->iso8601().'Z'; + + return ' + + + + ' . $username . ' + ' . digestBase64($nonce, $currentDate, $password) . ' + ' . $nonceBase64 . ' + ' . $currentDate . ' + + +'; +} + sub sendCmd { my $self = shift; my $cmd = shift; + my $msg_body = shift; + my $content_type = shift; my $result = undef; - printMsg($cmd, 'Tx'); + + my $msg = ' + '. + ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . + $msg_body . ' + '; + + $self->printMsg($cmd, 'Tx'); + + my $server_endpoint = $scheme.'://'.$address.':'.$port.$controlUri; + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => $content_type); + $req->header('Host' => $address . ':' . $port); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); - my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { - Error("Error check failed:'".$res->status_line()."'" ); + Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'\nMSG:$msg\nResponse:".$res->content); } - return $result; } sub getCamParams { my $self = shift; + my $msg = ' + + + + 000 + + + '; + + my $server_endpoint = $scheme.'://'.$address.':'.$port.'/onvif/imaging'; + + my $req = HTTP::Request->new(POST => $server_endpoint); + $req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"'); + $req->header('Host' => $address . ':' . $port); + $req->header('content-length' => length($msg)); + $req->header('accept-encoding' => 'gzip, deflate'); + $req->header('connection' => 'Close'); + $req->content($msg); - my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/get_camera_params.cgi'); my $res = $self->{ua}->request($req); if ( $res->is_success ) { - # Parse results setting values in %FCParams + # We should really use an xml or soap library to parse the xml tags my $content = $res->decoded_content; - while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { + if ( $content =~ /.*(.+)<\/tt:Brightness>.*/ ) { + $CamParams{$1} = $2; + } + if ( $content =~ /.*(.+)<\/tt:Contrast>.*/ ) { $CamParams{$1} = $2; } } else { - Error("Error check failed:'".$res->status_line()."'"); + Error("Unable to retrieve camera image settings:'".$res->status_line()."'"); } } @@ -101,256 +234,463 @@ sub getCamParams { #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; - my $stop_command = shift; my $autostop = shift; - if ( $stop_command && $autostop ) { + + if ( $autostop ) { Debug('Auto Stop'); + my $cmd = $controlUri; + my $msg_body = ' + + + '.$profileToken.' + + true + + + false + + + '; + + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; usleep($autostop); - my $cmd = 'decoder_control.cgi?command='.$stop_command; - $self->sendCmd($cmd); + $self->sendCmd($cmd, $msg_body, $content_type); } } -# Reset the Camera -sub reset { +# Reboot +sub reboot { + Debug('Camera reboot'); my $self = shift; + my $cmd = ''; + my $msg_body = << "END_MESSAGE"; + + + +END_MESSAGE + + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; + $self->sendCmd($cmd, $msg_body, $content_type); + +} + +# Reset(Reboot) the Camera +sub reset { Debug('Camera Reset'); - my $cmd = 'reboot.cgi?'; - $self->sendCmd($cmd); + my $self = shift; + my $cmd = ''; + my $msg_body = << "END_MESSAGE"; + + + +END_MESSAGE + + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"'; + $self->sendCmd($cmd, $msg_body, $content_type); +} + +sub moveMap { + my $self = shift; + my $params = shift; + my $x = $self->getParam($params,'xcoord'); + my $y = $self->getParam($params,'ycoord'); + Debug("Move map to $x x $y"); + + my $cmd = $controlUri; + my $msg_body =' + + + ' . $profileToken . ' + + + + + + + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); +} + +sub moveRel { + my $self = shift; + my $params = shift; + my $x = $self->getParam($params,'xcoord'); + my $speed = $self->getParam($params,'speed'); + my $y = $self->getParam($params,'ycoord'); + Debug("Move rel to $x x $y"); + + my $cmd = $controlUri; + my $msg_body =' + + + ' . $profileToken . ' + + + + + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); +} + +sub moveCamera { + my $type = shift; + my $x = shift; + my $y = shift; + my $msg_move_body = ""; + + if ( $type == "move" ){ + $msg_move_body = ' + + + '.$profileToken.' + + + + + '; + + } elsif ( $type == "zoom" ) { + $msg_move_body = ' + + + '.$profileToken.' + + + + + '; + } + + return $msg_move_body; + } #Up Arrow -sub moveConUp -{ +sub moveConUp { + Debug('Move Up'); my $self = shift; - my $stop_command = "1"; - Debug( "Move Up" ); - my $cmd = "decoder_control.cgi?command=0"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move", "0","0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } + #Down Arrow -sub moveConDown -{ +sub moveConDown { + Debug('Move Down'); my $self = shift; - my $stop_command = "3"; - Debug( "Move Down" ); - my $cmd = "decoder_control.cgi?command=2"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","0","-0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Left Arrow -sub moveConLeft -{ +sub moveConLeft { + Debug('Move Left'); my $self = shift; - my $stop_command = "5"; - Debug( "Move Left" ); - my $cmd = "decoder_control.cgi?command=4"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","-0.49","0"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Right Arrow -sub moveConRight -{ +sub moveConRight { + Debug('Move Right'); my $self = shift; - my $stop_command = "7"; - Debug( "Move Right" ); - my $cmd = "decoder_control.cgi?command=6"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","0.49","0"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom In -sub zoomConTele -{ +sub zoomConTele { + Debug('Zoom Tele'); my $self = shift; - my $stop_command = "17"; - Debug( "Zoom Tele" ); - my $cmd = "decoder_control.cgi?command=18"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("zoom","0.49","0"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Zoom Out -sub zoomConWide -{ +sub zoomConWide { + Debug('Zoom Wide'); my $self = shift; - my $stop_command = "19"; - Debug( "Zoom Wide" ); - my $cmd = "decoder_control.cgi?command=16"; - $self->sendCmd( $cmd ); - $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); + my $cmd = $controlUri; + my $msg_body = moveCamera("zoom","-0.49","0"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConUpRight -{ +sub moveConUpRight { + Debug('Move Diagonally Up Right'); my $self = shift; - Debug( "Move Diagonally Up Right" ); - $self->moveConUp( ); - $self->moveConRight( ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","0.5","0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConDownRight -{ +sub moveConDownRight { + Debug('Move Diagonally Down Right'); my $self = shift; - Debug( "Move Diagonally Down Right" ); - $self->moveConDown( ); - $self->moveConRight( ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","0.5","-0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConUpLeft -{ +sub moveConUpLeft { + Debug('Move Diagonally Up Left'); my $self = shift; - Debug( "Move Diagonally Up Left" ); - $self->moveConUp( ); - $self->moveConLeft( ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","-0.5","0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them -sub moveConDownLeft -{ +sub moveConDownLeft { + Debug('Move Diagonally Down Left'); my $self = shift; - Debug( "Move Diagonally Down Left" ); - $self->moveConDown( ); - $self->moveConLeft( ); + my $cmd = $controlUri; + my $msg_body = moveCamera("move","-0.5","-0.5"); + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); + $self->autoStop($self->{Monitor}->{AutoStopTimeout}); } #Stop -sub moveStop -{ +sub moveStop { + Debug('Move Stop'); my $self = shift; - Debug( "Move Stop" ); - my $cmd = "decoder_control.cgi?command=1"; - $self->sendCmd( $cmd ); + my $cmd = $controlUri; + my $msg_body = ' + + + '.$profileToken.' + true + false + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"'; + $self->sendCmd($cmd, $msg_body, $content_type); } #Set Camera Preset -#Presets must be translated into values internal to the camera -#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively -sub presetSet -{ +sub presetSet { my $self = shift; my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - - if (( $preset >= 1 ) && ( $preset <= 8 )) { - my $cmd = "decoder_control.cgi?command=".(($preset*2) + 28); - $self->sendCmd( $cmd ); - } + my $preset = $self->getParam($params, 'preset'); + Debug("Set Preset $preset"); + my $cmd = $controlUri; + my $msg_body =' + + + ' . $profileToken . ' + '.$preset.' + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"'; + $self->sendCmd($cmd, $msg_body, $content_type); } #Recall Camera Preset -#Presets must be translated into values internal to the camera -#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively -sub presetGoto -{ +sub presetGoto { my $self = shift; my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); + my $preset = $self->getParam($params, 'preset'); - if (( $preset >= 1 ) && ( $preset <= 8 )) { - my $cmd = "decoder_control.cgi?command=".(($preset*2) + 29); - $self->sendCmd( $cmd ); - } - - if ( $preset == 9 ) { - $self->horizontalPatrol(); - } - - if ( $preset == 10 ) { - $self->horizontalPatrolStop(); - } + Debug("Goto Preset $preset"); + my $cmd = $controlUri; + my $msg_body =' + + + ' . $profileToken . ' + '.$preset.' + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; + $self->sendCmd( $cmd, $msg_body, $content_type ); } -#Horizontal Patrol - Vertical Patrols are not supported -sub horizontalPatrol -{ +#Recall Camera Preset +sub presetHome { my $self = shift; - Debug( "Horizontal Patrol" ); - my $cmd = "decoder_control.cgi?command=20"; - $self->sendCmd( $cmd ); + my $params = shift; + + Debug("Goto Home preset"); + my $cmd = $controlUri; + my $msg_body =' + + + ' . $profileToken . ' + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"'; + $self->sendCmd( $cmd, $msg_body, $content_type ); +} + +#Horizontal Patrol +#To be determined if this camera supports this feature +sub horizontalPatrol { + Debug('Horizontal Patrol'); + my $self = shift; + my $cmd = ''; + my $msg =''; + my $content_type = ''; + # $self->sendCmd( $cmd, $msg, $content_type ); + Error('PTZ Command not implemented in control script.'); } #Horizontal Patrol Stop -sub horizontalPatrolStop -{ +#To be determined if this camera supports this feature +sub horizontalPatrolStop { + Debug('Horizontal Patrol Stop'); my $self = shift; - Debug( "Horizontal Patrol Stop" ); - my $cmd = "decoder_control.cgi?command=21"; - $self->sendCmd( $cmd ); + my $cmd = ''; + my $msg =''; + my $content_type = ''; + # $self->sendCmd( $cmd, $msg, $content_type ); + Error('PTZ Command not implemented in control script.'); } # Increase Brightness -sub irisAbsOpen -{ +sub irisAbsOpen { + Debug("Iris $CamParams{Brightness}"); my $self = shift; my $params = shift; - $self->getCamParams() unless($CamParams{'brightness'}); - my $step = $self->getParam( $params, 'step' ); + $self->getCamParams() unless($CamParams{Brightness}); + my $step = $self->getParam($params, 'step'); + my $max = 100; - $CamParams{'brightness'} += $step; - $CamParams{'brightness'} = 255 if ($CamParams{'brightness'} > 255); - Debug( "Iris $CamParams{'brightness'}" ); - my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; - $self->sendCmd( $cmd ); + $CamParams{Brightness} += $step; + $CamParams{Brightness} = $max if ($CamParams{Brightness} > $max); + + my $cmd = 'onvif/imaging'; + my $msg_body =' + + + 000 + + '.$CamParams{Brightness}.' + + true + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd($cmd, $msg_body, $content_type); } # Decrease Brightness -sub irisAbsClose -{ +sub irisAbsClose { + Debug("Iris $CamParams{Brightness}"); my $self = shift; my $params = shift; - $self->getCamParams() unless($CamParams{'brightness'}); - my $step = $self->getParam( $params, 'step' ); + $self->getCamParams() unless($CamParams{brightness}); + my $step = $self->getParam($params, 'step'); + my $min = 0; - $CamParams{'brightness'} -= $step; - $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); - Debug( "Iris $CamParams{'brightness'}" ); - my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; - $self->sendCmd( $cmd ); + $CamParams{Brightness} -= $step; + $CamParams{Brightness} = $min if ($CamParams{Brightness} < $min); + + my $cmd = 'onvif/imaging'; + my $msg_body =' + + + 000 + + '.$CamParams{Brightness}.' + + true + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd($cmd, $msg_body, $content_type); } # Increase Contrast -sub whiteAbsIn -{ +sub whiteAbsIn { + Debug("Iris $CamParams{Contrast}"); my $self = shift; my $params = shift; - $self->getCamParams() unless($CamParams{'contrast'}); - my $step = $self->getParam( $params, 'step' ); + $self->getCamParams() unless($CamParams{Contrast}); + my $step = $self->getParam($params, 'step'); + my $max = 100; - $CamParams{'contrast'} += $step; - $CamParams{'contrast'} = 6 if ($CamParams{'contrast'} > 6); - Debug( "Iris $CamParams{'contrast'}" ); - my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; - $self->sendCmd( $cmd ); + $CamParams{Contrast} += $step; + $CamParams{Contrast} = $max if ($CamParams{Contrast} > $max); + + my $cmd = 'onvif/imaging'; + my $msg_body =' + + + 000 + + '.$CamParams{Contrast}.' + + true + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd($cmd, $msg_body, $content_type); } # Decrease Contrast -sub whiteAbsOut -{ +sub whiteAbsOut { + Debug("Iris $CamParams{Contrast}"); my $self = shift; my $params = shift; - $self->getCamParams() unless($CamParams{'contrast'}); - my $step = $self->getParam( $params, 'step' ); + $self->getCamParams() unless($CamParams{Contrast}); + my $step = $self->getParam($params, 'step'); + my $min = 0; - $CamParams{'contrast'} -= $step; - $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); - Debug( "Iris $CamParams{'contrast'}" ); - my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; - $self->sendCmd( $cmd ); + $CamParams{Contrast} -= $step; + $CamParams{Contrast} = $min if ($CamParams{Contrast} < $min); + + my $cmd = 'onvif/imaging'; + my $msg_body =' + + + 000 + + '.$CamParams{Contrast}.' + + true + + '; + my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"'; + $self->sendCmd($cmd, $msg_body, $content_type); } 1; - +__END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 827936b86..18df301f3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -146,15 +146,15 @@ sub zmDbGetMonitors { if ( $function ) { if ( $function == DB_MON_CAPT ) { - $sql .= " where `Function` >= 'Monitor'"; + $sql .= " WHERE `Function` >= 'Monitor'"; } elsif ( $function == DB_MON_ACTIVE ) { - $sql .= " where `Function` > 'Monitor'"; + $sql .= " WHERE `Function` > 'Monitor'"; } elsif ( $function == DB_MON_MOTION ) { - $sql .= " where `Function` = 'Modect' or Function = 'Mocord'"; + $sql .= " WHERE `Function` = 'Modect' OR `Function` = 'Mocord'"; } elsif ( $function == DB_MON_RECORD ) { - $sql .= " where `Function` = 'Record' or Function = 'Mocord'"; + $sql .= " WHERE `Function` = 'Record' OR `Function` = 'Mocord'"; } elsif ( $function == DB_MON_PASSIVE ) { - $sql .= " where `Function` = 'Nodect'"; + $sql .= " WHERE `Function` = 'Nodect'"; } } my $sth = $dbh->prepare_cached( $sql ); @@ -266,15 +266,14 @@ sub end_transaction { } # end sub end_transaction # Basic execution of $dbh->do but with some pretty logging of the sql on error. -# Returns 1 on success, 0 on error sub zmDbDo { my $sql = shift; - if ( ! $dbh->do($sql, undef, @_) ) { + my $rows = $dbh->do($sql, undef, @_); + if ( ! defined $rows ) { $sql =~ s/\?/'%s'/; Error(sprintf("Failed $sql :", @_).$dbh->errstr()); - return 0; } - return 1; + return $rows; } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 0af41b2ca..48544a911 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -32,6 +32,7 @@ require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; require ZoneMinder::Frame; +require ZoneMinder::Monitor; require Date::Manip; require File::Find; require File::Path; @@ -41,7 +42,7 @@ require Number::Bytes::Human; require Date::Parse; require POSIX; use Date::Format qw(time2str); -use Time::HiRes qw(gettimeofday tv_interval); +use Time::HiRes qw(gettimeofday tv_interval stat); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -68,8 +69,8 @@ $serial = $primary_key = 'Id'; SecondaryStorageId Name Cause - StartTime - EndTime + StartDateTime + EndDateTime Width Height Length @@ -111,8 +112,8 @@ sub Time { $_[0]{Time} = $_[1]; } if ( ! defined $_[0]{Time} ) { - if ( $_[0]{StartTime} ) { - $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + if ( $_[0]{StartDateTime} ) { + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartDateTime} ); } } return $_[0]{Time}; @@ -349,6 +350,10 @@ sub GenerateVideo { return; } # end sub GenerateVideo +# Note about transactions, this function may be called with rows locked and hence in a transaction. +# So we will detect if we are in a transaction, and if not, start one. We will NOT do rollback or +# commits unless we started the transaction. + sub delete { my $event = $_[0]; @@ -360,11 +365,11 @@ sub delete { my $in_zmaudit = ( $0 =~ 'zmaudit.pl$'); if ( ! $in_zmaudit ) { - if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { + if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartDateTime} ) ) { # zmfilter shouldn't delete anything in an odd situation. zmaudit will though. my ( $caller, undef, $line ) = caller; - Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:". - (defined($event->{StartTime})?$event->{StartTime}:'undef')." from $caller:$line"); + Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartDateTime:". + (defined($event->{StartDateTime})?$event->{StartDateTime}:'undef')." from $caller:$line"); return; } if ( !($event->Storage()->Path() and -e $event->Storage()->Path()) ) { @@ -375,26 +380,28 @@ sub delete { if ( $$event{Id} ) { # Need to have an event Id if we are to delete from the db. - Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from ".$event->Path()); + Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartDateTime:$event->{StartDateTime} from ".$event->Path()); $ZoneMinder::Database::dbh->ping(); - $ZoneMinder::Database::dbh->begin_work(); - #$event->lock_and_load(); + my $in_transaction = $ZoneMinder::Database::dbh->{AutoCommit} ? 0 : 1; - ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id}); - if ( $ZoneMinder::Database::dbh->errstr() ) { - $ZoneMinder::Database::dbh->commit(); - return; - } + $ZoneMinder::Database::dbh->begin_work() if ! $in_transaction; + + # Going to delete in order of least value to greatest value. Stats is least and references Frames ZoneMinder::Database::zmDbDo('DELETE FROM Stats WHERE EventId=?', $$event{Id}); if ( $ZoneMinder::Database::dbh->errstr() ) { - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; + return; + } + ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id}); + if ( $ZoneMinder::Database::dbh->errstr() ) { + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; return; } # Do it individually to avoid locking up the table for new events ZoneMinder::Database::zmDbDo('DELETE FROM Events WHERE Id=?', $$event{Id}); - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if ! $in_transaction; } if ( ( $in_zmaudit or (!$Config{ZM_OPT_FAST_DELETE})) and $event->Storage()->DoDelete() ) { @@ -785,44 +792,50 @@ sub recover_timestamps { return; } my @contents = readdir(DIR); - Debug('Have ' . @contents . " files in $path"); + Debug('Have ' . @contents . ' files in '.$path); closedir(DIR); - my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents); + my @mp4_files = grep(/^\d+\-video\.mp4$/, @contents); if ( @mp4_files ) { $$Event{DefaultVideo} = $mp4_files[0]; } - my @analyse_jpgs = grep( /^\d+\-analyse\.jpg$/, @contents); + my @analyse_jpgs = grep(/^\d+\-analyse\.jpg$/, @contents); if ( @analyse_jpgs ) { - $$Event{Save_JPEGs} |= 2; + $$Event{SaveJPEGs} |= 2; } - my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents); + my @capture_jpgs = grep(/^\d+\-capture\.jpg$/, @contents); if ( @capture_jpgs ) { $$Event{Frames} = scalar @capture_jpgs; - $$Event{Save_JPEGs} |= 1; + $$Event{SaveJPEGs} |= 1; # can get start and end times from stat'ing first and last jpg @capture_jpgs = sort { $a cmp $b } @capture_jpgs; my $first_file = "$path/$capture_jpgs[0]"; ( $first_file ) = $first_file =~ /^(.*)$/; my $first_timestamp = (stat($first_file))[9]; - my $last_file = "$path/$capture_jpgs[@capture_jpgs-1]"; + my $last_file = $path.'/'.$capture_jpgs[@capture_jpgs-1]; ( $last_file ) = $last_file =~ /^(.*)$/; my $last_timestamp = (stat($last_file))[9]; my $duration = $last_timestamp - $first_timestamp; $Event->Length($duration); - $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); - $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); - Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}"); + $Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + if ( $Event->Scheme() eq 'Deep' and $Event->RelativePath(undef) and ($path ne $Event->Path(undef)) ) { + my ( $year, $month, $day, $hour, $minute, $second ) = + ($path =~ /(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})$/); + Error("Updating starttime to $path $year/$month/$day $hour:$minute:$second"); + $Event->StartDateTime(sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', 2000+$year, $month, $day, $hour, $minute, $second)); + } + $Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); + Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartDateTime} to $$Event{EndDateTime}"); $ZoneMinder::Database::dbh->begin_work(); foreach my $jpg ( @capture_jpgs ) { my ( $id ) = $jpg =~ /^(\d+)\-capture\.jpg$/; - if ( ! ZoneMinder::Frame->find_one( EventId=>$$Event{Id}, FrameId=>$id ) ) { - my $file = "$path/$jpg"; + if ( ! ZoneMinder::Frame->find_one(EventId=>$$Event{Id}, FrameId=>$id) ) { + my $file = $path.'/'.$jpg; ( $file ) = $file =~ /^(.*)$/; my $timestamp = (stat($file))[9]; my $Frame = new ZoneMinder::Frame(); @@ -833,11 +846,11 @@ sub recover_timestamps { Type=>'Normal', Score=>0, }); - } - } + } # end if Frame not found + } # end foreach capture jpg $ZoneMinder::Database::dbh->commit(); } elsif ( @mp4_files ) { - my $file = "$path/$mp4_files[0]"; + my $file = $path.'/'.$mp4_files[0]; ( $file ) = $file =~ /^(.*)$/; my $first_timestamp = (stat($file))[9]; @@ -852,8 +865,8 @@ sub recover_timestamps { } my $seconds = ($h*60*60)+($m*60)+$s; $Event->Length($seconds.'.'.$u); - $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); - $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) ); + $Event->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + $Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) ); } if ( @mp4_files ) { $Event->DefaultVideo($mp4_files[0]); @@ -903,6 +916,15 @@ sub canEdit { return 0; } # end sub canEdit +sub Monitor { + my $self = shift; + $$self{Monitor} = shift if @_; + if ( !$$self{Monitor} ) { + $$self{Monitor} = new ZoneMinder::Monitor($$self{MonitorId}); + } + return $$self{Monitor}; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index b48946d1f..5711437f2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -58,6 +58,7 @@ Id Name Query_json AutoArchive +AutoUnarchive AutoVideo AutoUpload AutoEmail @@ -76,6 +77,7 @@ UpdateDiskSpace UserId Background Concurrent +LockRows ); sub Execute { @@ -90,11 +92,13 @@ sub Execute { } if ( $self->{HasDiskPercent} ) { - my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : ()); + $$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage}; + my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS}); $sql =~ s/zmDiskPercent/$disk_percent/g; } if ( $self->{HasDiskBlocks} ) { - my $disk_blocks = getDiskBlocks(); + $$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage}; + my $disk_blocks = getDiskBlocks($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS}); $sql =~ s/zmDiskBlocks/$disk_blocks/g; } if ( $self->{HasSystemLoad} ) { @@ -102,6 +106,8 @@ sub Execute { $sql =~ s/zmSystemLoad/$load/g; } + $sql .= ' FOR UPDATE' if $$self{LockRows}; + Debug("Filter::Execute SQL ($sql)"); my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr()); @@ -144,25 +150,19 @@ sub Sql { } my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json}); - my $sql = 'SELECT E.*, - unix_timestamp(E.StartTime) as Time, - M.Name as MonitorName, - M.DefaultRate, - M.DefaultScale - FROM Events as E - INNER JOIN Monitors as M on M.Id = E.MonitorId - LEFT JOIN Storage as S on S.Id = E.StorageId - '; + my $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time + FROM Events as E'; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { # See getFilterQueryConjunctionTypes() if ( exists($term->{cnj}) and $term->{cnj} =~ /^(and|or)$/ ) { - $self->{Sql} .= ' '.$term->{cnj}.' '; + $self->{Sql} .= ' '.$term->{cnj}; } + $self->{Sql} .= ' '; if ( exists($term->{obr}) ) { - $self->{Sql} .= ' '.str_repeat('(', $term->{obr}).' '; + $self->{Sql} .= str_repeat('(', $term->{obr}).' '; } my $value = $term->{val}; my @value_list; @@ -170,165 +170,165 @@ sub Sql { if ( $term->{attr} eq 'AlarmedZoneId' ) { $term->{op} = 'EXISTS'; } elsif ( $term->{attr} =~ /^Monitor/ ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= 'M.'.$temp_attr_name; } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; $self->{Sql} .= 'M.ServerId'; } elsif ( $term->{attr} eq 'StorageServerId' ) { - $self->{Sql} .= 'S.ServerId'; + $self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)'; } elsif ( $term->{attr} eq 'FilterServerId' ) { $self->{Sql} .= $Config{ZM_SERVER_ID}; # StartTime options } elsif ( $term->{attr} eq 'DateTime' ) { - $self->{Sql} .= 'E.StartTime'; - } elsif ( $term->{attr} eq 'StartDateTime' ) { - $self->{Sql} .= 'E.StartTime'; + $self->{Sql} .= 'E.StartDateTime'; } elsif ( $term->{attr} eq 'Date' ) { - $self->{Sql} .= 'to_days( E.StartTime )'; + $self->{Sql} .= 'to_days( E.StartDateTime )'; } elsif ( $term->{attr} eq 'StartDate' ) { - $self->{Sql} .= 'to_days( E.StartTime )'; + $self->{Sql} .= 'to_days( E.StartDateTime )'; } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { - $self->{Sql} .= 'extract( hour_second from E.StartTime )'; + $self->{Sql} .= 'extract( hour_second from E.StartDateTime )'; } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { - $self->{Sql} .= 'weekday( E.StartTime )'; + $self->{Sql} .= 'weekday( E.StartDateTime )'; # EndTIme options } elsif ( $term->{attr} eq 'EndDateTime' ) { - $self->{Sql} .= 'E.EndTime'; + $self->{Sql} .= 'E.EndDateTime'; } elsif ( $term->{attr} eq 'EndDate' ) { - $self->{Sql} .= 'to_days( E.EndTime )'; - } elsif ( $term->{attr} eq 'EndTime' ) { - $self->{Sql} .= 'extract( hour_second from E.EndTime )'; + $self->{Sql} .= 'to_days( E.EndDateTime )'; + } elsif ( $term->{attr} eq 'EndDateTime' ) { + $self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; } elsif ( $term->{attr} eq 'EndWeekday' ) { - $self->{Sql} .= "weekday( E.EndTime )"; - -# + $self->{Sql} .= "weekday( E.EndDateTime )"; } elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { push @{$self->{PostSQLConditions}}, $term; - } elsif ( $term->{attr} eq 'DiskSpace' ) { - $self->{Sql} .= 'E.DiskSpace'; + $self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; } elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= 'zmDiskPercent'; $self->{HasDiskPercent} = !undef; - $self->{HasPreCondition} = !undef; } elsif ( $term->{attr} eq 'DiskBlocks' ) { $self->{Sql} .= 'zmDiskBlocks'; $self->{HasDiskBlocks} = !undef; - $self->{HasPreCondition} = !undef; } elsif ( $term->{attr} eq 'SystemLoad' ) { $self->{Sql} .= 'zmSystemLoad'; $self->{HasSystemLoad} = !undef; - $self->{HasPreCondition} = !undef; } else { $self->{Sql} .= 'E.'.$term->{attr}; } - ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - - if ( $term->{attr} eq 'AlarmedZoneId' ) { - $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND ZoneId='.$value.')'; - } elsif ( $term->{attr} =~ /^MonitorName/ ) { - $value = "'$temp_value'"; - } elsif ( $term->{attr} =~ /ServerId/) { - Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); - if ( $temp_value eq 'ZM_SERVER_ID' ) { - $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; - # This gets used later, I forget for what - $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); - } elsif ( $temp_value eq 'NULL' ) { - $value = $temp_value; - } else { + if ( $term->{attr} eq 'ExistsInFileSystem' ) { + # PostCondition, so no further SQL + } else { + ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; + foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { + + if ( $term->{attr} eq 'AlarmedZoneId' ) { + $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; + } elsif ( $term->{attr} =~ /^MonitorName/ ) { $value = "'$temp_value'"; - # This gets used later, I forget for what - $$self{Server} = new ZoneMinder::Server($temp_value); - } - } elsif ( $term->{attr} eq 'StorageId' ) { - $value = "'$temp_value'"; - $$self{Storage} = new ZoneMinder::Storage($temp_value); - } elsif ( $term->{attr} eq 'Name' + } elsif ( $term->{attr} =~ /ServerId/) { + Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); + if ( $temp_value eq 'ZM_SERVER_ID' ) { + $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); + } elsif ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } else { + $value = "'$temp_value'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server($temp_value); + } + } elsif ( $term->{attr} eq 'StorageId' ) { + $value = "'$temp_value'"; + $$self{Storage} = new ZoneMinder::Storage($temp_value); + } elsif ( $term->{attr} eq 'Name' || $term->{attr} eq 'Cause' || $term->{attr} eq 'Notes' - ) { + ) { if ( $term->{op} eq 'LIKE' - || $term->{op} eq 'NOT LIKE' + || $term->{op} eq 'NOT LIKE' ) { - $temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/; + $temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/; + } + $value = "'$temp_value'"; + } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "'$value'"; + } + } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) { + $value = 'to_days('.$temp_value.')'; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "to_days( '$value' )"; + } + } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { + if ( $temp_value eq 'NULL' ) { + $value = $temp_value; + } else { + $value = DateTimeToSQL($temp_value); + if ( !$value ) { + Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); + return; + } + $value = "extract( hour_second from '$value' )"; } - $value = "'$temp_value'"; - } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { - if ( $temp_value eq 'NULL' ) { - $value = $temp_value; } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "'$value'"; - } - } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { - if ( $temp_value eq 'NULL' ) { $value = $temp_value; - } elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) { - $value = 'to_days('.$temp_value.')'; - } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "to_days( '$value' )"; } - } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { - if ( $temp_value eq 'NULL' ) { - $value = $temp_value; + push @value_list, $value; + } # end foreach temp_value + } # end if has an attr + + if ( $term->{op} ) { + if ( $term->{op} eq '=~' ) { + $self->{Sql} .= ' REGEXP '.$value; + } elsif ( $term->{op} eq '!~' ) { + $self->{Sql} .= ' NOT REGEXP '.$value; + } elsif ( $term->{op} eq 'IS' ) { + if ( $value eq 'Odd' ) { + $self->{Sql} .= ' % 2 = 1'; + } elsif ( $value eq 'Even' ) { + $self->{Sql} .= ' % 2 = 0'; } else { - $value = DateTimeToSQL($temp_value); - if ( !$value ) { - Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'"); - return; - } - $value = "extract( hour_second from '$value' )"; + $self->{Sql} .= ' IS '.$value; } + } elsif ( $term->{op} eq 'EXISTS' ) { + $self->{Sql} .= ' EXISTS '.$value; + } elsif ( $term->{op} eq 'IS NOT' ) { + $self->{Sql} .= ' IS NOT '.$value; + } elsif ( $term->{op} eq '=[]' or $term->{op} eq 'IN' ) { + $self->{Sql} .= ' IN ('.join(',', @value_list).")"; + } elsif ( $term->{op} eq '![]' ) { + $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; + } elsif ( $term->{op} eq 'LIKE' ) { + $self->{Sql} .= ' LIKE '.$value; + } elsif ( $term->{op} eq 'NOT LIKE' ) { + $self->{Sql} .= ' NOT LIKE '.$value; } else { - $value = $temp_value; + $self->{Sql} .= ' '.$term->{op}.' '.$value; } - push @value_list, $value; - } # end foreach temp_value - } # end if has an attr - if ( $term->{op} ) { - if ( $term->{op} eq '=~' ) { - $self->{Sql} .= " regexp $value"; - } elsif ( $term->{op} eq '!~' ) { - $self->{Sql} .= " not regexp $value"; - } elsif ( $term->{op} eq 'IS' ) { - if ( $value eq 'Odd' ) { - $self->{Sql} .= ' % 2 = 1'; - } elsif ( $value eq 'Even' ) { - $self->{Sql} .= ' % 2 = 0'; - } else { - $self->{Sql} .= " IS $value"; - } - } elsif ( $term->{op} eq 'EXISTS' ) { - $self->{Sql} .= " EXISTS $value"; - } elsif ( $term->{op} eq 'IS NOT' ) { - $self->{Sql} .= " IS NOT $value"; - } elsif ( $term->{op} eq '=[]' ) { - $self->{Sql} .= ' IN ('.join(',', @value_list).')'; - } elsif ( $term->{op} eq '!~' ) { - $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; - } elsif ( $term->{op} eq 'LIKE' ) { - $self->{Sql} .= " LIKE $value"; - } elsif ( $term->{op} eq 'NOT LIKE' ) { - $self->{Sql} .= " NOT LIKE $value"; - } else { - $self->{Sql} .= ' '.$term->{op}.' '.$value; - } - } # end if has an operator - if ( exists($term->{cbr}) ) { - $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}).' '; - } + } # end if has an operator + } # end if Pre/Post or SQL + $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}) if exists($term->{cbr}); + $self->{Sql} .= "\n"; } # end foreach term } # end if terms @@ -341,6 +341,9 @@ sub Sql { if ( $self->{AutoArchive} ) { push @auto_terms, 'E.Archived = 0'; } + if ( $self->{AutoUnarchive} ) { + push @auto_terms, 'E.Archived = 1'; + } # Don't do this, it prevents re-generation and concatenation. # If the file already exists, then the video won't be re-recreated if ( $self->{AutoVideo} ) { @@ -362,20 +365,26 @@ sub Sql { $sql .= ' AND ( '.join(' or ', @auto_terms).' )'; } if ( !$filter_expr->{sort_field} ) { - $filter_expr->{sort_field} = 'StartTime'; + $filter_expr->{sort_field} = 'StartDateTime'; $filter_expr->{sort_asc} = 0; } my $sort_column = ''; if ( $filter_expr->{sort_field} eq 'Id' ) { $sort_column = 'E.Id'; } elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) { + $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName + FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId'; $sort_column = 'M.Name'; } elsif ( $filter_expr->{sort_field} eq 'Name' ) { $sort_column = 'E.Name'; + } elsif ( $filter_expr->{sort_field} eq 'StartDateTime' ) { + $sort_column = 'E.StartDateTime'; } elsif ( $filter_expr->{sort_field} eq 'StartTime' ) { - $sort_column = 'E.StartTime'; + $sort_column = 'E.StartDateTime'; } elsif ( $filter_expr->{sort_field} eq 'EndTime' ) { - $sort_column = 'E.EndTime'; + $sort_column = 'E.EndDateTime'; + } elsif ( $filter_expr->{sort_field} eq 'EndDateTime' ) { + $sort_column = 'E.EndDateTime'; } elsif ( $filter_expr->{sort_field} eq 'Secs' ) { $sort_column = 'E.Length'; } elsif ( $filter_expr->{sort_field} eq 'Frames' ) { @@ -391,10 +400,10 @@ sub Sql { } elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) { $sort_column = 'E.DiskSpace'; } else { - $sort_column = 'E.StartTime'; + $sort_column = 'E.StartDateTime'; } my $sort_order = $filter_expr->{sort_asc} ? 'ASC' : 'DESC'; - $sql .= ' ORDER BY '.$sort_column." ".$sort_order; + $sql .= ' ORDER BY '.$sort_column.' '.$sort_order; if ( $filter_expr->{limit} ) { $sql .= ' LIMIT 0,'.$filter_expr->{limit}; } @@ -414,7 +423,7 @@ sub getDiskPercent { } sub getDiskBlocks { - my $command = 'df .'; + my $command = 'df ' . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 0d3784185..d68967fa9 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -262,21 +262,21 @@ sub createEvent { } $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; - $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); + $event->{StartDateTime} = $frame->{TimeStamp} unless ( $event->{StartDateTime} ); $event->{TotScore} += $frame->{Score}; $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); - $event->{EndTime} = $frame->{TimeStamp}; + $event->{EndDateTime} = $frame->{TimeStamp}; $lastTimestamp = $frame->{TimeStamp}; } $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); - $event->{Length} = $event->{EndTime} - $event->{StartTime}; + $event->{Length} = $event->{EndDateTime} - $event->{StartDateTime}; my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', + StartDateTime => 'from_unixtime(?)', + EndDateTime => 'from_unixtime(?)', ); my ( @fields, @formats, @values ); @@ -297,7 +297,7 @@ sub createEvent { $event->{Id} = $dbh->{mysql_insertid}; Info( "Created event ".$event->{Id} ); - if ( $event->{EndTime} ) { + if ( $event->{EndDateTime} ) { $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} if ( $event->{Name} eq 'New Event' ); my $sql = "update Events set Name = ? where Id = ?"; @@ -383,8 +383,8 @@ sub updateEvent { if ( $event->{Name} eq 'New Event' ); my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', + StartDateTime => 'from_unixtime(?)', + EndDateTime => 'from_unixtime(?)', ); my ( @values, @sets ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 0513f563e..43ae38e72 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -42,6 +42,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( constants => [ qw( + DEBUG9 + DEBUG8 + DEBUG7 + DEBUG6 + DEBUG5 + DEBUG4 + DEBUG3 + DEBUG2 + DEBUG1 DEBUG INFO WARNING @@ -98,6 +107,15 @@ use Time::HiRes qw/gettimeofday/; use Sys::Syslog; use constant { + DEBUG9 => 9, + DEBUG8 => 8, + DEBUG7 => 7, + DEBUG6 => 6, + DEBUG5 => 5, + DEBUG4 => 4, + DEBUG3 => 3, + DEBUG2 => 2, + DEBUG1 => 1, DEBUG => 1, INFO => 0, WARNING => -1, @@ -108,7 +126,16 @@ use constant { }; our %codes = ( - &DEBUG => 'DBG', + &DEBUG9 => 'DB9', + &DEBUG8 => 'DB8', + &DEBUG7 => 'DB7', + &DEBUG6 => 'DB6', + &DEBUG5 => 'DB5', + &DEBUG4 => 'DB4', + &DEBUG3 => 'DB3', + &DEBUG2 => 'DB2', + &DEBUG1 => 'DB1', + &DEBUG => 'DB1', &INFO => 'INF', &WARNING => 'WAR', &ERROR => 'ERR', @@ -159,7 +186,7 @@ sub new { ( $this->{fileName} = $0 ) =~ s|^.*/||; $this->{logPath} = $ZoneMinder::Config::Config{ZM_PATH_LOGS}; $this->{logFile} = $this->{logPath}.'/'.$this->{id}.'.log'; - ($this->{logFile}) = $this->{logFile} =~ /^([\w\.\/]+)$/; + ($this->{logFile}) = $this->{logFile} =~ /^([_\-\w\.\/]+)$/; $this->{trace} = 0; @@ -332,9 +359,9 @@ sub reinitialise { sub limit { my $this = shift; my $level = shift; - return(DEBUG) if $level > DEBUG; - return(NOLOG) if $level < NOLOG; - return($level); + return DEBUG9 if $level > DEBUG9; + return NOLOG if $level < NOLOG; + return $level; } sub getTargettedEnv { @@ -504,9 +531,9 @@ sub openFile { $LOGFILE->autoflush() if $this->{autoFlush}; my $webUid = (getpwnam($ZoneMinder::Config::Config{ZM_WEB_USER}))[2]; - Error("Can't get uid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webUid; + Error('Can\'t get uid for '.$ZoneMinder::Config::Config{ZM_WEB_USER}) if ! defined $webUid; my $webGid = (getgrnam($ZoneMinder::Config::Config{ZM_WEB_GROUP}))[2]; - Error("Can't get gid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webGid; + Error('Can\'t get gid for '.$ZoneMinder::Config::Config{ZM_WEB_USER}) if ! defined $webGid; if ( $> == 0 ) { # If we are root, we want to make sure that www-data or whatever owns the file chown($webUid, $webGid, $this->{logFile} ) or @@ -610,6 +637,7 @@ sub logInit( ;@ ) { my %options = @_ ? @_ : (); $logger = ZoneMinder::Logger->new() if !$logger; $logger->initialise(%options); + logSetSignal(); } sub logReinit { @@ -626,12 +654,26 @@ sub logHupHandler { $do_log_rotate = 1; } +sub logUSR1Handler { + $logger->level($logger->level()+1); + Info('Logger - Level changed to '. $logger->level() . '=>'.$codes{$logger->level()}); +} + +sub logUSR2Handler { + $logger->level($logger->level()-1); + Info('Logger - Level changed to '. $logger->level() . '=>'.$codes{$logger->level()}); +} + sub logSetSignal { $SIG{HUP} = \&logHupHandler; + $SIG{USR1} = \&logUSR1Handler; + $SIG{USR2} = \&logUSR2Handler; } sub logClearSignal { $SIG{HUP} = 'DEFAULT'; + $SIG{USR1} = 'DEFAULT'; + $SIG{USR2} = 'DEFAULT'; } sub logLevel { @@ -674,38 +716,34 @@ sub Dump { sub debug { my $log = shift; - $log->logPrint(DEBUG, @_, caller); + $log->logPrint(DEBUG1, @_, caller); } -sub Debug( @ ) { - fetch()->logPrint(DEBUG, @_, caller); +sub Debug { + fetch()->logPrint( + (@_ == 1 ? (DEBUG1, @_) : @_), + caller); } -sub Info( @ ) { - fetch()->logPrint(INFO, @_, caller); -} +sub Info { fetch()->logPrint(INFO, @_, caller); } sub info { my $log = shift; $log->logPrint(INFO, @_, caller); } -sub Warning( @ ) { - fetch()->logPrint(WARNING, @_, caller); -} +sub Warning { fetch()->logPrint(WARNING, @_, caller); } sub warn { my $log = shift; $log->logPrint(WARNING, @_, caller); } -sub Error( @ ) { - fetch()->logPrint(ERROR, @_, caller); -} +sub Error { fetch()->logPrint(ERROR, @_, caller); } sub error { my $log = shift; $log->logPrint(ERROR, @_, caller); } -sub Fatal( @ ) { +sub Fatal { my $this = fetch(); $this->logPrint(FATAL, @_, caller); if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) { @@ -720,7 +758,7 @@ sub Fatal( @ ) { exit(-1); } -sub Panic( @ ) { +sub Panic { fetch()->logPrint(PANIC, @_, caller); confess($_[0]); } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index c52a12309..e7fdcd35f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -144,9 +144,11 @@ our $mem_seq = 0; our $mem_data = { shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { size => { type=>'uint32', seq=>$mem_seq++ }, - last_write_index => { type=>'uint32', seq=>$mem_seq++ }, - last_read_index => { type=>'uint32', seq=>$mem_seq++ }, + last_write_index => { type=>'int32', seq=>$mem_seq++ }, + last_read_index => { type=>'int32', seq=>$mem_seq++ }, state => { type=>'uint32', seq=>$mem_seq++ }, + capture_fps => { type=>'double', seq=>$mem_seq++ }, + analysis_fps => { type=>'double', seq=>$mem_seq++ }, last_event => { type=>'uint64', seq=>$mem_seq++ }, action => { type=>'uint32', seq=>$mem_seq++ }, brightness => { type=>'int32', seq=>$mem_seq++ }, @@ -160,14 +162,17 @@ our $mem_data = { signal => { type=>'uint8', seq=>$mem_seq++ }, format => { type=>'uint8', seq=>$mem_seq++ }, imagesize => { type=>'uint32', seq=>$mem_seq++ }, - epadding1 => { type=>'uint32', seq=>$mem_seq++ }, + last_frame_score => { type=>'uint32', seq=>$mem_seq++ }, + audio_frequency => { type=>'uint32', seq=>$mem_seq++ }, + audio_channels => { type=>'uint32', seq=>$mem_seq++ }, startup_time => { type=>'time_t64', seq=>$mem_seq++ }, zmc_heartbeat_time => { type=>'time_t64', seq=>$mem_seq++ }, - zma_heartbeat_time => { type=>'time_t64', seq=>$mem_seq++ }, last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, control_state => { type=>'uint8[256]', seq=>$mem_seq++ }, alarm_cause => { type=>'int8[256]', seq=>$mem_seq++ }, + video_fifo => { type=>'int8[64]', seq=>$mem_seq++ }, + audio_fifo => { type=>'int8[64]', seq=>$mem_seq++ }, } }, trigger_data => { type=>'TriggerData', seq=>$mem_seq++, 'contents'=> { @@ -214,7 +219,7 @@ sub zmMemInit { || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; - } elsif ($member_data->{type} eq 'int16' + } elsif ( $member_data->{type} eq 'int16' || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; @@ -223,6 +228,8 @@ sub zmMemInit { || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; + } elsif ( $member_data->{type} eq 'double' ) { + $member_data->{size} = $member_data->{align} = 8; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { $member_data->{size} = $1; $member_data->{align} = 1; @@ -236,7 +243,7 @@ sub zmMemInit { $offset += ($member_data->{align} - ($offset%$member_data->{align})); } $member_data->{offset} = $offset; - $offset += $member_data->{size} + $offset += $member_data->{size}; } $section_data->{size} = $offset - $section_data->{offset}; } @@ -299,7 +306,7 @@ sub zmMemVerify { Error("Shared data not valid for monitor $$monitor{Id}"); return undef; } else { - Debug("Shared data appears valid for monitor $$monitor{Id}: $valid"); + Debug(3, "Shared data appears valid for monitor $$monitor{Id}: $valid"); } return !undef; @@ -322,7 +329,7 @@ sub zmMemRead { my $size = $mem_data->{$section}->{contents}->{$element}->{size}; if (!defined $offset || !defined $type || !defined $size) { - Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead'); + Error('Invalid field:'.$field.' setting to undef and exiting zmMemRead offset:'.$offset.' type:'.$type.' size:'.$size); zmMemInvalidate($monitor); return undef; } @@ -355,7 +362,9 @@ sub zmMemRead { } elsif ( $type eq 'int8' ) { ( $value ) = unpack('c', $data); } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { - ( $value ) = unpack( 'C', $data ); + ( $value ) = unpack('C', $data); + } elsif ( $type eq 'double' ) { + ( $value ) = unpack('d', $data); } elsif ( $type =~ /^int8\[\d+\]$/ ) { ( $value ) = unpack('Z'.$size, $data); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index cb7920913..d76bbae9a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -81,7 +81,7 @@ sub zmMemAttach { return undef; } if ( defined($monitor->{MMapAddr}) ) { - Debug("zmMemAttach already attached at $monitor->{MMapAddr} for $$monitor{Id}"); + Debug(3, "zmMemAttach already attached at $monitor->{MMapAddr} for $$monitor{Id}"); return !undef; } @@ -168,11 +168,11 @@ sub zmMemPut { } sub zmMemClean { - Debug('Removing memory map files'); + Debug(3, 'Removing memory map files'); my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; foreach my $mapFile( glob( $mapPath ) ) { ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug("Removing memory map file '$mapFile'"); + Debug(3, "Removing memory map file '$mapFile'"); unlink($mapFile); } } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm index fe2cfe115..a0aeafab0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm @@ -151,14 +151,14 @@ sub zmMemPut sub zmMemClean { - Debug( "Removing shared memory\n" ); + Debug(2, 'Removing shared memory'); # Find ZoneMinder shared memory - my $command = "ipcs -m | grep '^" - .substr( sprintf( "0x%x", hex($Config{ZM_SHM_KEY}) ), 0, -2 ) - ."'" + my $command = 'ipcs -m | grep \'^' + .substr( sprintf( '0x%x', hex($Config{ZM_SHM_KEY}) ), 0, -2 ) + .'\'' ; - Debug( "Checking for shared memory with '$command'\n" ); - open( my $CMD, '<', "$command |" ) + Debug(2, 'Checking for shared memory with '.$command); + open( my $CMD, '<', $command.' |' ) or Fatal( "Can't execute '$command': $!" ); while( <$CMD> ) { @@ -167,8 +167,8 @@ sub zmMemClean if ( $id =~ /^(\d+)/ ) { $id = $1; - $command = "ipcrm shm $id"; - Debug( "Removing shared memory with '$command'\n" ); + $command = 'ipcrm shm '.$id; + Debug(2, 'Removing shared memory with '.$command); qx( $command ); } } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 963f75638..de29b8dd0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -27,11 +27,15 @@ package ZoneMinder::Monitor; use 5.006; use strict; use warnings; +use Time::HiRes qw(usleep); require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; require ZoneMinder::Server; +require ZoneMinder::Memory; +require ZoneMinder::Monitor_Status; +require ZoneMinder::Zone; #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); @@ -114,6 +118,7 @@ $serial = $primary_key = 'Id'; TrackDelay ReturnLocation ReturnDelay + ModectDuringPTZ DefaultRate DefaultScale SignalCheckPoints @@ -121,6 +126,11 @@ $serial = $primary_key = 'Id'; WebColour Exif Sequence + ZoneCount + Refresh + DefaultCodec + Latitude + Longitude ); %defaults = ( @@ -194,6 +204,7 @@ $serial = $primary_key = 'Id'; TrackDelay => undef, ReturnLocation => -1, ReturnDelay => undef, + ModectDuringPTZ => 0, DefaultRate => 100, DefaultScale => 100, SignalCheckPoints => 0, @@ -201,6 +212,11 @@ $serial = $primary_key = 'Id'; WebColour => '#ff0000', Exif => 0, Sequence => undef, + ZoneCount => 0, + Refresh => undef, + DefaultCodec => 'auto', + Latitude => undef, + Longitude => undef, ); sub Server { @@ -211,6 +227,76 @@ sub Storage { return new ZoneMinder::Storage( $_[0]{StorageId} ); } # end sub Storage +sub Zones { + if (! exists $_[0]{Zones}) { + $_[0]{Zones} = [ $_[0]{Id} ? ZoneMinder::Zone->find(MonitorId=>$_[0]{Id}) : () ]; + } + return wantarray ? @{$_[0]{Zones}} : $_[0]{Zones}; +} + +sub control { + my $monitor = shift; + my $command = shift; + my $process = shift; + + if ( $command eq 'stop' or $command eq 'restart' ) { + if ( $process ) { + `/usr/bin/zmdc.pl stop $process -m $$monitor{Id}`; + } else { + `/usr/bin/zmdc.pl stop zma -m $$monitor{Id}`; + `/usr/bin/zmdc.pl stop zmc -m $$monitor{Id}`; + } + } + if ( $command eq 'start' or $command eq 'restart' ) { + if ( $process ) { + `/usr/bin/zmdc.pl start $process -m $$monitor{Id}`; + } else { + `/usr/bin/zmdc.pl start zmc -m $$monitor{Id}`; + `/usr/bin/zmdc.pl start zma -m $$monitor{Id}`; + } # end if + } +} # end sub control + +sub Status { + my $self = shift; + $$self{Status} = shift if @_; + if ( ! $$self{Status} ) { + $$self{Status} = ZoneMinder::Monitor_Status->find_one(MonitorId=>$$self{Id}); + } + return $$self{Status}; +} + +sub connect { + my $self = shift; + return ZoneMinder::Memory::zmMemVerify($self); +} + +sub disconnect { + my $self = shift; + ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end +} + +sub suspendMotionDetection { + my $self = shift; + return 0 if ! ZoneMinder::Memory::zmMemVerify($self); + while (ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { + ZoneMinder::Logger::Debug(1, 'Suspending motion detection'); + ZoneMinder::Memory::zmMonitorSuspend($self); + usleep(100000); + } + ZoneMinder::Logger::Debug(1,ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)); +} + +sub resumeMotionDetection { + my $self = shift; + return 0 if ! ZoneMinder::Memory::zmMemVerify($self); + #while (zmMemRead($self, 'shared_data:active', 1)) { + ZoneMinder::Logger::Debug(1, 'Resuming motion detection'); + ZoneMinder::Memory::zmMonitorResume($self); + #} + return 1; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm new file mode 100644 index 000000000..9a9077653 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm @@ -0,0 +1,107 @@ +# ========================================================================== +# +# ZoneMinder Monitor_STatus Module +# Copyright (C) 2020 ZoneMinder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor_Status; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Monitor_Status'; +$serial = $primary_key = 'MonitorId'; +%fields = map { $_ => $_ } qw( + MonitorId + Status + CaptureFPS + AnalysisFPS + CaptureBandwidth + TotalEvents + TotalEventDiskSpace + HourEvents + HourEventDiskSpace + DayEvents + DayEventDiskSpace + WeekEvents + WeekEventDiskSpace + MonthEvents + MonthEventDiskSpace + ArchivedEvents + ArchivedEventDiskSpace + ); + +%defaults = ( + Status => 'Unknown', + CaptureFPS => undef, + AnalysisFPS => undef, + CaptureBandwidth => undef, + TotalEvents => undef, + TotalEventDiskSpace => undef, + HourEvents => undef, + HourEventDiskSpace => undef, + DayEvents => undef, + DayEventDiskSpace => undef, + WeekEvents => undef, + WeekEventDiskSpace => undef, + MonthEvents => undef, + MonthEventDiskSpace => undef, + ArchivedEvents => undef, + ArchivedEventDiskSpace => undef, + ); + +sub Monitor { + return new ZoneMinder::Monitor( $_[0]{MonitorId} ); +} # end sub Monitor + +1; +__END__ + +=head1 NAME + +ZoneMinder::Monitor_Status - Perl Class for Monitor Status + +=head1 SYNOPSIS + +use ZoneMinder::Monitor_Status; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index 569b3de9d..f3d750338 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -22,6 +22,12 @@ # This module contains the common definitions and functions used by the rest # of the ZoneMinder scripts # + +sub array_diff(\@\@) { + my %e = map { $_ => undef } @{$_[1]}; + return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] }; +} + package ZoneMinder::Object; use 5.006; @@ -40,6 +46,10 @@ our @ISA = qw(ZoneMinder::Base); # # ========================================================================== +sub def_or_undef { + return defined($_[0]) ? $_[0] : 'undef'; +} + use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); @@ -370,40 +380,29 @@ sub set { $log->error("$type -> set called with non-hash params from $caller $line"); } - foreach my $field ( keys %fields ) { - if ( $params ) { - $log->debug("field: $field, param: ".$$params{$field}) if $debug; - if ( exists $$params{$field} ) { - $log->debug("field: $field, $$self{$field} =? param: ".$$params{$field}) if $debug; - if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { -# Only make changes to fields that have changed - if ( defined $fields{$field} ) { - $$self{$field} = $$params{$field} if defined $fields{$field}; - push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating - } # end if - $log->debug("Running $field with $$params{$field}") if $debug; - if ( my $func = $self->can( $field ) ) { - $func->( $self, $$params{$field} ); - } # end if - } # end if - } # end if - } # end if $params - - if ( defined $fields{$field} ) { - if ( $$self{$field} ) { - $$self{$field} = transform( $type, $field, $$self{$field} ); - } # end if $$self{field} - } - } # end foreach field + if ( $params ) { + foreach my $field ( keys %{$params} ) { + $log->debug("field: $field, ".def_or_undef($$self{$field}).' =? param: '.def_or_undef($$params{$field})) if $debug; + if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { + # Only make changes to fields that have changed + if ( defined $fields{$field} ) { + $$self{$field} = $$params{$field}; + push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating + } # end if has a column + $log->debug("Running $field with $$params{$field}") if $debug; + if ( my $func = $self->can( $field ) ) { + $func->( $self, $$params{$field} ); + } # end if has function + } # end if has change + } # end foreach field + } # end if $params foreach my $field ( keys %defaults ) { - if ( ( ! exists $$self{$field} ) or (!defined $$self{$field}) or ( $$self{$field} eq '' ) ) { - $log->debug("Setting default ($field) ($$self{$field}) ($defaults{$field}) ") if $debug; + $log->debug("Setting default ($field) (".def_or_undef($$self{$field}).') ('.def_or_undef($defaults{$field}).') ') if $debug; if ( defined $defaults{$field} ) { - $log->debug("Default $field is defined: $defaults{$field}") if $debug; - if ( $defaults{$field} eq 'NOW()' ) { - $$self{$field} = 'NOW()'; + if ( $defaults{$field} eq '' or $defaults{$field} eq 'NOW()' ) { + $$self{$field} = $defaults{$field}; } else { $$self{$field} = eval($defaults{$field}); $log->error( "Eval error of object default $field default ($defaults{$field}) Reason: " . $@ ) if $@; @@ -411,8 +410,9 @@ sub set { } else { $$self{$field} = $defaults{$field}; } # end if -#$$self{$field} = ( defined $defaults{$field} ) ? eval($defaults{$field}) : $defaults{$field}; - $log->debug("Setting default for ($field) using ($defaults{$field}) to ($$self{$field}) ") if $debug; + $log->debug("Setting default for ($field) using (".def_or_undef($defaults{$field}).') to ('.def_or_undef($$self{$field}).') ') if $debug; + } elsif ( defined $fields{$field} and $$self{$field} ) { + $$self{$field} = transform( $type, $field, $$self{$field} ); } # end if } # end foreach default return @set_fields; @@ -852,6 +852,42 @@ sub find_sql { return \%sql; } # end sub find_sql +sub changes { + my ( $self, $params ) = @_; + + my $type = ref $self; + if ( ! $type ) { + my ( $caller, undef, $line ) = caller; + $log->error("No type in Object::changes. self:$self from $caller:$line"); + } + my $fields = eval ('\%'.$type.'::fields'); + if (!$fields) { + $log->warn('Object::changes called on an object with no fields'); + return; + } # end if + my @results; + + foreach my $field (sort keys %$fields) { + next if ! exists $$params{$field}; + + if ( ref $$self{$field} eq 'ARRAY' ) { + my @second = ref $$params{$field} eq 'ARRAY' ? @{$$params{$field}} : ($$params{$field}); + if (array_diff(@{$$self{$field}}, @second)) { + push @results, [ $field, $$self{$field}, $$params{$field} ]; + } + } elsif ( + (!defined($$self{$field}) and defined($$params{$field})) + or + (defined($$self{$field}) and !defined($$params{$field})) + ) { + push @results, $field; + } elsif ( defined($$self{$field}) and defined($$params{$field}) and ($$self{$field} ne $$params{$field}) ) { + push @results, $field; + } + } + return @results; +} + sub AUTOLOAD { my $type = ref($_[0]); Carp::cluck("No type in autoload") if ! $type; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm new file mode 100644 index 000000000..986c5a4dc --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm @@ -0,0 +1,105 @@ +# ========================================================================== +# +# ZoneMinder Zone Module +# Copyright (C) 2020 ZoneMinder LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== + +package ZoneMinder::Zone; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Zones'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + MonitorId + Type + Units + CheckMethod + MinPixelThreshold + MaxPixelThreshold + MinAlarmPixels + MaxAlarmPixels + FilterX + FilterY + MinFilterPixels + MaxFilterPixels + MinBlobPixels + MaxBlobPixels + MinBlobs + MaxBlobs + OverloadFrames + ExtendAlarmFrames + ); + +%defaults = ( + Name => '', + Type => 'Active', + Units => 'Pixels', + CheckMethod => 'Blobs', + MinPixelThreshold => undef, + MaxPixelThreshold => undef, + MinAlarmPixels => undef, + MaxAlarmPixels => undef, + FilterX => undef, + FilterY => undef, + MinFilterPixels => undef, + MaxFilterPixels => undef, + MinBlobPixels => undef, + MaxBlobPixels => undef, + MinBlobs => undef, + MaxBlobs => undef, + OverloadFrames => 0, + ExtendAlarmFrames => 0, +); + +1; +__END__ + +=head1 NAME + +ZoneMinder::Zone - Perl Class for Zones + +=head1 SYNOPSIS + +use ZoneMinder::Zone; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 80a227e2b..180aee58f 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -205,7 +205,7 @@ MAIN: while( $loop ) { my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = 'SELECT `Id`, (unix_timestamp() - unix_timestamp(`StartTime`)) AS Age + my $eventSelectSql = 'SELECT `Id`, (unix_timestamp() - unix_timestamp(`StartDateTime`)) AS Age FROM `Events` WHERE `MonitorId` = ?'.(@Storage_Areas ? ' AND `StorageId` IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY `Id`'; my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); @@ -308,7 +308,7 @@ MAIN: while( $loop ) { } else { my $full_path = join('/', $Storage->Path(), $day_dir, $event_path); # Check storage id - if ( !$Event->Storage()->Id() ) { + if ( $Storage->Id() and !$Event->Storage()->Id() ) { Info("Correcting StorageId for event $$Event{Id} from $$Event{StorageId} $$Event{Path} to $$Storage{Id} $full_path"); $Event->save({ StorageId=>$Storage->Id() }); $Event->Path(undef); @@ -397,13 +397,13 @@ MAIN: while( $loop ) { my ( undef, $year, $month, $day ) = split('/', $day_dir); $year += 2000; my ( $hour, $minute, $second ) = split('/', $event_dir); - my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); + my $StartDateTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); my $Event = ZoneMinder::Event->find_one( MonitorId=>$monitor_dir, - StartTime=>$StartTime, + StartDateTime=>$StartDateTime, ); if ( $Event ) { - Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); + Debug("Found event matching StartDateTime on monitor $monitor_dir at $StartDateTime: " . $Event->to_string()); next; } aud_print("Deleting event directories with no event id information at $day_dir/$event_dir"); @@ -440,7 +440,7 @@ MAIN: while( $loop ) { $Event->Path(); $Event->age(); Debug("Have event $$Event{Id} at $$Event{Path}"); - $Event->StartTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())))); + $Event->StartDateTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())))); } # end foreach event } @@ -592,7 +592,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; } - if ( !$Event->StartTime() ) { + if ( !$Event->StartDateTime() ) { aud_print("Event $$Event{Id} has no start time."); if ( confirm() ) { $Event->delete(); @@ -600,7 +600,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } next; } - if ( ! $Event->EndTime() ) { + if ( ! $Event->EndDateTime() ) { if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { aud_print("Event $$Event{Id} has no end time and is $age seconds old. Deleting it."); if ( confirm() ) { @@ -639,9 +639,9 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}"); $Event->StorageId($$fs_events{$db_event}->StorageId()); } - if ( ! $Event->StartTime() ) { - Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}"); - $Event->StartTime($$fs_events{$db_event}->StartTime()); + if ( ! $Event->StartDateTime() ) { + Info("Updating StartDateTime on event $$Event{Id} from $$Event{StartDateTime} to $$fs_events{$db_event}{StartDateTime}"); + $Event->StartDateTime($$fs_events{$db_event}->StartDateTime()); } $Event->save(); @@ -683,12 +683,12 @@ if ( $level > 1 ) { # Remove empty events (with no frames) $cleaned = 0; Debug("Checking for Events with no Frames"); - my $selectEmptyEventsSql = 'SELECT `E`.`Id` AS `Id`, `E`.`StartTime`, `F`.`EventId` FROM `Events` AS E LEFT JOIN `Frames` AS F ON (`E`.`Id` = `F`.`EventId`) - WHERE isnull(`F`.`EventId`) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > `E`.`StartTime`'; + my $selectEmptyEventsSql = 'SELECT `E`.`Id` AS `Id`, `E`.`StartDateTime`, `F`.`EventId` FROM `Events` AS E LEFT JOIN `Frames` AS F ON (`E`.`Id` = `F`.`EventId`) + WHERE isnull(`F`.`EventId`) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > `E`.`StartDateTime`'; if ( my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) ) { if ( $res = $selectEmptyEventsSth->execute() ) { while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { - aud_print("Found empty event with no frame records '$event->{Id}' at $$event{StartTime}"); + aud_print("Found empty event with no frame records '$event->{Id}' at $$event{StartDateTime}"); if ( confirm() ) { if ( $res = $deleteEventSth->execute($event->{Id}) ) { $cleaned = 1; @@ -750,7 +750,7 @@ if ( $level > 1 ) { #"SELECT E.Id, ANY_VALUE(E.MonitorId), # #max(F.TimeStamp) as EndTime, -#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, +#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartDateTime) as Length, #max(F.FrameId) as Frames, #count(if(F.Score>0,1,NULL)) as AlarmFrames, #sum(F.Score) as TotScore, @@ -760,11 +760,11 @@ if ( $level > 1 ) { #WHERE isnull(E.Frames) or isnull(E.EndTime) #GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}.' second)' #; - 'SELECT *, unix_timestamp(`StartTime`) AS `TimeStamp` FROM `Events` WHERE `EndTime` IS NULL AND `StartTime` < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'.($monitor_id?' AND MonitorId=?':''); + 'SELECT *, unix_timestamp(`StartDateTime`) AS `TimeStamp` FROM `Events` WHERE `EndDateTime` IS NULL AND `StartDateTime` < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'.($monitor_id?' AND MonitorId=?':''); my $selectFrameDataSql = ' SELECT - max(`TimeStamp`) AS `EndTime`, + max(`TimeStamp`) AS `EndDateTime`, unix_timestamp(max(`TimeStamp`)) AS `EndTimeStamp`, max(`FrameId`) AS `Frames`, count(if(`Score`>0,1,NULL)) AS `AlarmFrames`, @@ -779,7 +779,7 @@ FROM `Frames` WHERE `EventId`=?'; my $updateUnclosedEventsSql = "UPDATE low_priority `Events` SET `Name` = ?, - `EndTime` = ?, + `EndDateTime` = ?, `Length` = ?, `Frames` = ?, `AlarmFrames` = ?, @@ -794,7 +794,7 @@ FROM `Frames` WHERE `EventId`=?'; $res = $selectUnclosedEventsSth->execute($monitor_id?$monitor_id:()) or Fatal("Can't execute: ".$selectUnclosedEventsSth->errstr()); while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { - aud_print("Found open event '$event->{Id}' on Monitor $event->{MonitorId} at $$event{StartTime}"); + aud_print("Found open event '$event->{Id}' on Monitor $event->{MonitorId} at $$event{StartDateTime}"); if ( confirm('close', 'closing') ) { if ( ! ( $res = $selectFrameDataSth->execute($event->{Id}) ) ) { Error("Can't execute: $selectFrameDataSql:".$selectFrameDataSth->errstr()); @@ -808,7 +808,7 @@ FROM `Frames` WHERE `EventId`=?'; $event->{Id}, RECOVER_TAG ), - $frame->{EndTime}, + $frame->{EndDateTime}, $frame->{EndTimeStamp} - $event->{TimeStamp}, $frame->{Frames}, $frame->{AlarmFrames}, @@ -885,11 +885,11 @@ FROM `Frames` WHERE `EventId`=?'; $loop = $continuous; my $eventcounts_sql = ' - UPDATE `Monitors` SET - `TotalEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id`), - `TotalEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `DiskSpace` IS NOT NULL), - `ArchivedEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `Archived`=1), - `ArchivedEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitors`.`Id` AND `Archived`=1 AND `DiskSpace` IS NOT NULL) + UPDATE `Event_Summaries` SET + `TotalEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId`), + `TotalEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `DiskSpace` IS NOT NULL), + `ArchivedEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `Archived`=1), + `ArchivedEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Event_Summaries`.`MonitorId` AND `Archived`=1 AND `DiskSpace` IS NOT NULL) '; my $eventcounts_sth = $dbh->prepare_cached( $eventcounts_sql ); @@ -897,40 +897,40 @@ FROM `Frames` WHERE `EventId`=?'; $eventcounts_sth->finish(); my $eventcounts_hour_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `HourEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `HourEventDiskSpace` FROM `Events_Hour` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`HourEvents` = `E`.`HourEvents`, - `Monitors`.`HourEventDiskSpace` = `E`.`HourEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`HourEvents` = `E`.`HourEvents`, + `Event_Summaries`.`HourEventDiskSpace` = `E`.`HourEventDiskSpace` '; my $eventcounts_day_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `DayEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `DayEventDiskSpace` FROM `Events_Day` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`DayEvents` = `E`.`DayEvents`, - `Monitors`.`DayEventDiskSpace` = `E`.`DayEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`DayEvents` = `E`.`DayEvents`, + `Event_Summaries`.`DayEventDiskSpace` = `E`.`DayEventDiskSpace` '; my $eventcounts_week_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `WeekEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `WeekEventDiskSpace` FROM `Events_Week` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`WeekEvents` = `E`.`WeekEvents`, - `Monitors`.`WeekEventDiskSpace` = `E`.`WeekEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`WeekEvents` = `E`.`WeekEvents`, + `Event_Summaries`.`WeekEventDiskSpace` = `E`.`WeekEventDiskSpace` '; my $eventcounts_month_sql = ' - UPDATE `Monitors` INNER JOIN ( + UPDATE `Event_Summaries` INNER JOIN ( SELECT `MonitorId`, COUNT(*) AS `MonthEvents`, SUM(COALESCE(`DiskSpace`,0)) AS `MonthEventDiskSpace` FROM `Events_Month` GROUP BY `MonitorId` - ) AS `E` ON `E`.`MonitorId`=`Monitors`.`Id` SET - `Monitors`.`MonthEvents` = `E`.`MonthEvents`, - `Monitors`.`MonthEventDiskSpace` = `E`.`MonthEventDiskSpace` + ) AS `E` ON `E`.`MonitorId`=`Event_Summaries`.`MonitorId` SET + `Event_Summaries`.`MonthEvents` = `E`.`MonthEvents`, + `Event_Summaries`.`MonthEventDiskSpace` = `E`.`MonthEventDiskSpace` '; my $eventcounts_hour_sth = $dbh->prepare_cached($eventcounts_hour_sql); my $eventcounts_day_sth = $dbh->prepare_cached($eventcounts_day_sql); diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 74886b4d4..d1f50cf43 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -27,7 +27,7 @@ use strict; use ZoneMinder; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); -use POSIX qw/strftime EPIPE/; +use POSIX qw/strftime EPIPE EINTR/; use Socket; use Data::Dumper; use Module::Load::Conditional qw{can_load}; @@ -83,7 +83,7 @@ if ( $options{command} ) { my $server_up; while ( $tries and ! ( $server_up = connect(CLIENT, $saddr) ) ) { Debug("Failed to connect to zmcontrol server at $sock_file"); - runCommand("zmdc.pl start zmcontrol.pl --id=$id"); + runCommand("zmdc.pl start zmcontrol.pl --id $id"); sleep 1; $tries -= 1; } @@ -138,13 +138,21 @@ if ( $options{command} ) { Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); } + my $zm_terminate = 0; + sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; + } + $SIG{TERM} = \&TermHandler; + $SIG{INT} = \&TermHandler; + Info("Control server $id/$protocol starting at " .strftime('%y/%m/%d %H:%M:%S', localtime()) ); - $0 = $0." --id=$id"; + $0 = $0.' --id '.$id; - my $control = "ZoneMinder::Control::$protocol"->new($id); + my $control = ('ZoneMinder::Control::'.$protocol)->new($id); my $control_key = $control->getKey(); $control->loadMonitor(); @@ -162,24 +170,24 @@ if ( $options{command} ) { listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); my $rin = ''; - vec( $rin, fileno(SERVER), 1 ) = 1; + vec($rin, fileno(SERVER), 1) = 1; my $win = $rin; my $ein = $win; my $timeout = MAX_COMMAND_WAIT; - while( 1 ) { + while (!$zm_terminate) { my $nfound = select(my $rout = $rin, undef, undef, $timeout); if ( $nfound > 0 ) { - if ( vec( $rout, fileno(SERVER), 1 ) ) { + if ( vec($rout, fileno(SERVER), 1) ) { my $paddr = accept(CLIENT, SERVER); my $message = ; + close(CLIENT); next if !$message; my $params = jsonDecode($message); - Debug( Dumper( $params ) ); + Debug(Dumper($params)); my $command = $params->{command}; - close(CLIENT); if ( $command eq 'quit' ) { last; } elsif ( $command ) { @@ -191,23 +199,23 @@ if ( $options{command} ) { Fatal('Bogus descriptor'); } } elsif ( $nfound < 0 ) { - if ( $! == EPIPE ) { + if ( $! == EINTR ) { + # Likely just SIGHUP + Debug("Can't select: $!"); + } elsif ( $! == EPIPE ) { Error("Can't select: $!"); } else { Fatal("Can't select: $!"); } } else { - #print( "Select timed out\n" ); - last; + Debug('Select timed out'); } - } # end while forever + } # end while !$zm_terminate Info("Control server $id/$protocol exiting"); unlink($sock_file); $control->close(); - exit(0); } # end if !server up - exit(0); 1; diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 3ab910469..5cf866e56 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -92,7 +92,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', - 'zma', 'zmfilter.pl', 'zmaudit.pl', 'zmtrigger.pl', @@ -102,11 +101,12 @@ my @daemons = ( 'zmstats.pl', 'zmtrack.pl', 'zmcontrol.pl', + 'zm_rtsp_server', 'zmtelemetry.pl' ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons,'zmeventnotification.pl'; + push @daemons, 'zmeventnotification.pl'; } my $command = shift @ARGV; @@ -239,7 +239,7 @@ use Sys::MemInfo qw(totalmem freemem totalswap freeswap); use ZoneMinder::Server qw(CpuLoad); #use Data::Dumper; -use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sending KILL +use constant KILL_DELAY => 10; # seconds to wait between sending TERM and sending KILL our %cmd_hash; our %pid_hash; @@ -436,18 +436,19 @@ sub start { return; } + # We have to block SIGCHLD during fork to prevent races while we setup our records for it my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); # Apparently the child closing the db connection can affect the parent. zmDbDisconnect(); - if ( my $cpid = fork() ) { + if ( my $child_pid = fork() ) { $dbh = zmDbConnect(1); # This logReinit is required. Not sure why. logReinit(); - $process->{pid} = $cpid; + $process->{pid} = $child_pid; $process->{started} = time(); delete $process->{pending}; @@ -456,9 +457,9 @@ sub start { .", pid = $process->{pid}\n" ); - $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; + $cmd_hash{$process->{command}} = $pid_hash{$child_pid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); - } elsif ( defined($cpid) ) { + } elsif ( defined($child_pid) ) { # Child process # Force reconnection to the db. $dbh got copied, but isn't really valid anymore. @@ -490,7 +491,7 @@ sub start { zmDbDisconnect(); my $fd = 3; # leave stdin,stdout,stderr open. Closing them causes problems with libx264 - while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { + while ( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } @@ -611,6 +612,7 @@ sub restart { # Start will be handled by the reaper... # unless it was already pending in which case send_stop will return () so we should start it if ( !send_stop(0, $process) ) { + dPrint(ZoneMinder::Logger::WARNING, "!send_stop so starting '$command'\n"); start($daemon, @args); } return; diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 5ff316078..60d6293b4 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -98,6 +98,7 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) ; logInit($filter_id?(id=>'zmfilter_'.$filter_id):()); + sub HupHandler { # This idea at this time is to just exit, freeing up the memory. # zmfilter.pl will be respawned by zmdc. @@ -162,16 +163,17 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -if ( ! EVENT_PATH ) { - Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}"); +if ( !EVENT_PATH ) { + Error('No event path defined. Config was '.$Config{ZM_DIR_EVENTS}); die; } # In future, should not be neccessary wrt StorageAreas -chdir( EVENT_PATH ); +chdir(EVENT_PATH); # Should not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); +$dbh->do('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED'); if ( $filter_name ) { Info("Scanning for events using filter '$filter_name'"); @@ -236,6 +238,7 @@ sub getFilters { $sql .= ' `Background` = 1 AND'; } $sql .= '( `AutoArchive` = 1 + or `AutoUnarchive` = 1 or `AutoVideo` = 1 or `AutoUpload` = 1 or `AutoEmail` = 1 @@ -275,6 +278,7 @@ FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { sub checkFilter { my $filter = shift; + my $in_transaction = ZoneMinder::Database::start_transaction($dbh) if $$filter{LockRows}; my @Events = $filter->Execute(); Debug( join(' ', @@ -282,6 +286,7 @@ sub checkFilter { join(', ', ($filter->{AutoDelete}?'delete':()), ($filter->{AutoArchive}?'archive':()), + ($filter->{AutoUnarchive}?'unarchive':()), ($filter->{AutoVideo}?'video':()), ($filter->{AutoUpload}?'upload':()), ($filter->{AutoEmail}?'email':()), @@ -299,7 +304,7 @@ sub checkFilter { last if $zm_terminate; my $Event = new ZoneMinder::Event($$event{Id}, $event); - Debug("Checking event $Event->{Id}"); + Debug('Checking event '.$Event->{Id}); my $delete_ok = !undef; $dbh->ping(); if ( $filter->{AutoArchive} ) { @@ -311,6 +316,15 @@ sub checkFilter { my $res = $sth->execute($Event->{Id}) or Error("Unable to execute '$sql': ".$dbh->errstr()); } + if ( $filter->{AutoUnarchive} ) { + Info("Unarchiving event $Event->{Id}"); + # Do it individually to avoid locking up the table for new events + my $sql = 'UPDATE `Events` SET `Archived` = 0 WHERE `Id` = ?'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($Event->{Id}) + or Error("Unable to execute '$sql': ".$dbh->errstr()); + } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { if ( !$Event->{Videoed} ) { $delete_ok = undef if !generateVideo($filter, $Event); @@ -346,6 +360,7 @@ sub checkFilter { if ( $filter->{AutoMove} ) { my $NewStorage = new ZoneMinder::Storage($filter->{AutoMoveTo}); + Info("Moving event $Event->{Id} to datastore $filter->{AutoMoveTo}"); $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } @@ -354,6 +369,7 @@ sub checkFilter { # So we still need to update the Event object with the new SecondaryStorageId my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo}); if ( $NewStorage ) { + Info("Copying event $Event->{Id} to datastore $filter->{AutoCopyTo}"); $_ = $Event->CopyTo($NewStorage); if ( $_ ) { $ZoneMinder::Database::dbh->commit(); @@ -368,8 +384,10 @@ sub checkFilter { } # end if AutoCopy if ( $filter->{UpdateDiskSpace} ) { - $ZoneMinder::Database::dbh->begin_work(); - $Event->lock_and_load(); + if ( $$filter{LockRows} ) { + $ZoneMinder::Database::dbh->begin_work(); + $Event->lock_and_load(); + } my $old_diskspace = $$Event{DiskSpace}; my $new_diskspace = $Event->DiskSpace(undef); @@ -381,9 +399,10 @@ sub checkFilter { ) { $Event->save(); } - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if !$$filter{LockRows}; } # end if UpdateDiskSpace } # end foreach event + ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; } # end sub checkFilter sub generateVideo { @@ -391,8 +410,9 @@ sub generateVideo { my $Event = shift; my $phone = shift; - my $rate = $Event->{DefaultRate}/100; - my $scale = $Event->{DefaultScale}/100; + my $Monitor = $Event->Monitor(); + my $rate = $$Monitor{DefaultRate}/100; + my $scale = $$Monitor{DefaultScale}/100; my $format; my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS}); @@ -641,12 +661,14 @@ sub substituteTags { # First we'd better check what we need to get # We have a filter and an event, do we need any more # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; + my $Status = $Monitor->Status() if $need_status; # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD)%/; + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/; my $first_alarm_frame; my $max_alarm_frame; my $max_alarm_score = 0; @@ -673,13 +695,13 @@ sub substituteTags { my $url = $Config{ZM_URL}; $text =~ s/%ZP%/$url/g; - $text =~ s/%MN%/$Event->{MonitorName}/g; - $text =~ s/%MET%/$Monitor->{TotalEvents}/g; - $text =~ s/%MEH%/$Monitor->{HourEvents}/g; - $text =~ s/%MED%/$Monitor->{DayEvents}/g; - $text =~ s/%MEW%/$Monitor->{WeekEvents}/g; - $text =~ s/%MEM%/$Monitor->{MonthEvents}/g; - $text =~ s/%MEA%/$Monitor->{ArchivedEvents}/g; + $text =~ s/%MN%/$Monitor->{Name}/g; + $text =~ s/%MET%/$Status->{TotalEvents}/g; + $text =~ s/%MEH%/$Status->{HourEvents}/g; + $text =~ s/%MED%/$Status->{DayEvents}/g; + $text =~ s/%MEW%/$Status->{WeekEvents}/g; + $text =~ s/%MEM%/$Status->{MonthEvents}/g; + $text =~ s/%MEA%/$Status->{ArchivedEvents}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; @@ -690,7 +712,7 @@ sub substituteTags { $text =~ s/%EN%/$Event->{Name}/g; $text =~ s/%EC%/$Event->{Cause}/g; $text =~ s/%ED%/$Event->{Notes}/g; - $text =~ s/%ET%/$Event->{StartTime}/g; + $text =~ s/%ET%/$Event->{StartDateTime}/g; $text =~ s/%EVF%/$$Event{DefaultVideo}/g; # Event video filename $text =~ s/%EL%/$Event->{Length}/g; $text =~ s/%EF%/$Event->{Frames}/g; @@ -704,6 +726,11 @@ sub substituteTags { $text =~ s/%EPFM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; $text =~ s/%EPI1%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; $text =~ s/%EPIM%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + $text =~ s/%EPFMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; + $text =~ s/%EPIMOD%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; + $text =~ s/%EPFMODG%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect_gif/g; + $text =~ s/%EPIMODG%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect_gif/g; + if ( $attachments_ref ) { if ( $text =~ s/%EI1%//g ) { my $path = generateImage($Event, $first_alarm_frame); @@ -751,9 +778,7 @@ sub substituteTags { } } - if ( $text =~ s/%EIMOD%//g or $text =~ s/%EFMOD%//g ) { - $text =~ s/%EFMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; - $text =~ s/%EIMOD%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; + if ( $text =~ s/%EIMOD%//g ) { my $path = $Event->Path().'/objdetect.jpg'; if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; @@ -762,6 +787,15 @@ sub substituteTags { } } + if ( $text =~ s/%EIMODG%//g ) { + my $path = $Event->Path().'/objdetect.gif'; + if ( -e $path ) { + push @$attachments_ref, { type=>'image/gif', path=>$path }; + } else { + Warning('No image for MODG at '.$path); + } + } + } # end if attachments_ref } # end if $first_alarm_frame diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 4b2032a8d..2f6708fc8 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -171,12 +171,21 @@ if ( $command =~ /^(?:start|restart)$/ ) { if ( $Config{ZM_DYN_DB_VERSION} and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION ) ) { - Fatal('Version mismatch, system is version '.ZM_VERSION - .', database is '.$Config{ZM_DYN_DB_VERSION} - .', please run zmupdate.pl to update.' - ); - exit(-1); - } + my ( $db_major, $db_minor, $db_micro ) = split(/\./, $Config{ZM_DYN_DB_VERSION}); + my ( $major, $minor, $micro ) = split(/\./, ZM_VERSION); + if ( $db_major != $major or $db_minor != $minor ) { + Fatal('Version mismatch, system is version '.ZM_VERSION + .', database is '.$Config{ZM_DYN_DB_VERSION} + .', please run zmupdate.pl to update.' + ); + exit(-1); + } else { + Error('Version mismatch, system is version '.ZM_VERSION + .', database is '.$Config{ZM_DYN_DB_VERSION} + .', please run zmupdate.pl to update.' + ); + } + } # end if version mismatch # Recreate the temporary directory if it's been wiped verifyFolder('@ZM_TMPDIR@'); @@ -216,9 +225,6 @@ if ( $command =~ /^(?:start|restart)$/ ) { } else { runCommand("zmdc.pl start zmc -m $monitor->{Id}"); } - if ( $monitor->{Function} ne 'Monitor' ) { - runCommand("zmdc.pl start zma -m $monitor->{Id}"); - } if ( $Config{ZM_OPT_CONTROL} ) { if ( $monitor->{Controllable} ) { runCommand("zmdc.pl start zmcontrol.pl --id $monitor->{Id}"); @@ -289,6 +295,9 @@ if ( $command =~ /^(?:start|restart)$/ ) { } else { runCommand('zmdc.pl start zmstats.pl'); } + if ( $Config{ZM_MIN_RTSP_PORT} ) { + runCommand('zmdc.pl start zm_rtsp_server'); + } } else { $retval = 1; } diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 58647b946..d6fe578f6 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -224,6 +224,7 @@ Debug("@Monitors"); $$Event{RelativePath} = join('/', $day_dir, $event_path); $$Event{Scheme} = 'Deep'; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); @@ -231,7 +232,7 @@ Debug("@Monitors"); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -287,6 +288,7 @@ Debug("@Monitors"); $$Event{RelativePath} = join('/', $day_dir, $event_dir); $$Event{Scheme} = 'Deep'; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); $Event->Height( $Monitor->Height() ); @@ -294,7 +296,7 @@ Debug("@Monitors"); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -309,13 +311,13 @@ Debug("@Monitors"); my ( undef, $year, $month, $day ) = split('/', $day_dir); $year += 2000; my ( $hour, $minute, $second ) = split('/', $event_dir); - my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); + my $StartDateTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); my $Event = ZoneMinder::Event->find_one( MonitorId=>$monitor_dir, - StartTime=>$StartTime, + StartDateTime=>$StartDateTime, ); if ( $Event ) { - Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); + Debug("Found event matching starttime on monitor $monitor_dir at $StartDateTime: " . $Event->to_string()); next; } @@ -352,13 +354,14 @@ Debug("@Monitors"); $$Event{Scheme} = 'Medium'; $$Event{RelativePath} = $event_dir; $$Event{Name} = "Event $event_id recovered"; + $$Event{StateId} = 1; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { @@ -393,6 +396,7 @@ Debug("@Monitors"); if ( confirm() ) { $$Event{Scheme} = 'Shallow'; $$Event{Name} = "Event $event recovered"; + $$Event{StateId} = 1; #$$Event{Path} = $event_path; $Event->MonitorId( $monitor_dir ); $Event->Width( $Monitor->Width() ); @@ -400,7 +404,7 @@ Debug("@Monitors"); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - if ( $$Event{StartTime} ) { + if ( $$Event{StartDateTime} ) { $Event->save({}, 1); Info("Event resurrected as " . $Event->to_string() ); } else { diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 555d9c602..3c86aada6 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -28,13 +28,20 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Stats Daemon starting in '.START_DELAY.' seconds'); sleep(START_DELAY); my $dbh = zmDbConnect(); -while( 1 ) { +while (!$zm_terminate) { while ( ! ( $dbh and $dbh->ping() ) ) { Info('Reconnecting to db'); if ( !($dbh = zmDbConnect()) ) { @@ -43,10 +50,10 @@ while( 1 ) { } } - $dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr()); - $dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Hour WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Day WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Week WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr()); + $dbh->do('DELETE FROM Events_Month WHERE StartDateTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr()); # Prune the Logs table if required if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { @@ -60,14 +67,8 @@ while( 1 ) { my $row = $selectLogRowCountSth->fetchrow_hashref(); my $logRows = $row->{Rows}; if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { - my $deleteLogByRowsSql = 'DELETE low_priority FROM `Logs` ORDER BY `TimeKey` ASC LIMIT ?'; - my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) - or Fatal("Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr()); - $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) - or Fatal("Can't execute: ".$deleteLogByRowsSth->errstr()); - if ( $deleteLogByRowsSth->rows() ) { - Debug('Deleted '.$deleteLogByRowsSth->rows().' log table entries by count'); - } + my $rows = zmDbDo('DELETE low_priority FROM `Logs` ORDER BY `TimeKey` ASC LIMIT ?', $logRows - $Config{ZM_LOG_DATABASE_LIMIT}); + Debug('Deleted '.$rows.' log table entries by count') if defined $rows; } } else { # Time of record @@ -76,23 +77,19 @@ while( 1 ) { if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) { $Config{ZM_LOG_DATABASE_LIMIT} = $1; } - my $deleted_rows; + my $rows; do { - my $deleteLogByTimeSql = - 'DELETE FROM `Logs` - WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100'; - my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) - or Fatal("Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr()); - my $res = $deleteLogByTimeSth->execute() - or Fatal("Can't execute: ".$deleteLogByTimeSth->errstr()); - $deleted_rows = $deleteLogByTimeSth->rows(); - Debug("Deleted $deleted_rows log table entries by time"); - } while ( $deleted_rows ); + my $rows = zmDbDo('DELETE FROM `Logs` WHERE `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100'); + Debug("Deleted $rows log table entries by time") if defined $rows; + } while ($rows); } } # end if ZM_LOG_DATABASE_LIMIT + # Delete any sessions that are more ethan a week old. Limiting to 100 because mysql sucks + zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - (60*60*24*7)); + sleep($Config{ZM_STATS_UPDATE_INTERVAL}); -} # end while (1) +} # end while (!$zm_terminate) Info('Stats Daemon exiting'); exit(); diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 1ee392c1e..041d1c449 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -87,6 +87,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Trigger daemon starting'); @@ -118,7 +125,7 @@ my $win = $rin; my $ein = $win; my $timeout = SELECT_TIMEOUT; my %actions; -while (1) { +while (!$zm_terminate) { $rin = $base_rin; # Add the file descriptors of any spawned connections foreach my $fileno ( keys %spawned_connections ) { @@ -187,7 +194,7 @@ while (1) { } else { Fatal("Can't select: $!"); } - } # end if select returned activitiy + } # end if select returned activity # Check polled connections foreach my $connection ( @in_poll_connections ) { @@ -219,10 +226,10 @@ while (1) { #print( "$monitor->{Id}: S:$state, LE:$last_event" ); #print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" ); - if ( $state == STATE_ALARM || $state == STATE_ALERT ) { + if ( $state == STATE_ALARM or $state == STATE_ALERT ) { # In alarm state if ( !defined($monitor->{LastEvent}) - || ($last_event != $monitor->{LastEvent}) + or ($last_event != $monitor->{LastEvent}) ) { # A new event push @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event; @@ -231,9 +238,9 @@ while (1) { # Do nothing } } elsif ( - ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) - || - ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) + (($state == STATE_IDLE) and ($monitor->{LastState} != STATE_IDLE)) + or + (($state == STATE_TAPE) and ($monitor->{LastState} != STATE_TAPE)) ) { # Out of alarm state push @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event; @@ -266,7 +273,7 @@ while (1) { Debug('Checking for timed actions'); my $now = time(); foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) { - Info("Found " . scalar @{$actions{$action_time}} . "actions expiring at $action_time"); + Info('Found '.(scalar @{$actions{$action_time}}).'actions expiring at '.$action_time); foreach my $action ( @{$actions{$action_time}} ) { my $connection = $action->{connection}; Info("Found action '$$action{message}'"); @@ -312,14 +319,14 @@ while (1) { # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); -} # end while ( 1 ) +} # end while (!$zm_terminate) Info('Trigger daemon exiting'); exit; sub loadMonitor { my $monitor = shift; - Debug("Loading monitor $monitor"); + Debug('Loading monitor '.$monitor); zmMemInvalidate($monitor); if ( zmMemVerify($monitor) ) { # This will re-init shared memory @@ -334,7 +341,7 @@ sub loadMonitors { my %new_monitors = (); my $sql = 'SELECT * FROM `Monitors` - WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect\' )'. + WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'. ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' ) ; my $sth = $dbh->prepare_cached( $sql ) @@ -350,7 +357,7 @@ sub loadMonitors { $new_monitors{$monitor->{Id}} = $monitor; } # end while fetchrow %monitors = %new_monitors; -} +} # end sub loadMonitors sub handleMessage { my $connection = shift; @@ -369,12 +376,17 @@ sub handleMessage { my $monitor = $monitors{$id}; if ( !$monitor ) { - Warning("Can't find monitor '$id' for message '$message'"); + loadMonitors(); + $monitor = $monitors{$id}; + if ( !$monitor ) { + Warning("Can't find monitor '$id' for message '$message'"); + return; + } + } + if ( !zmMemVerify($monitor) ) { + Warning("Can't verify monitor '$id' for message '$message'"); return; } - Debug("Found monitor for id '$id'"); - - next if !zmMemVerify($monitor); Debug("Handling action '$action'"); if ( $action =~ /^(enable|disable)(?:[\+ ](\d+))?$/ ) { @@ -387,21 +399,25 @@ sub handleMessage { } # Force a reload $monitor_reload_time = 0; - Info("Set monitor to $state"); + Info('Set monitor to '.$state); if ( $delay ) { - my $action_text = $id.'|'.( ($state eq 'enable') - ? 'disable' - : 'enable' - ); + my $action_text = $id.'|'.(($state eq 'enable') ? 'disable' : 'enable'); handleDelay($delay, $connection, $action_text); } } elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ ) { - next if !$monitor->{Enabled}; + if ( !$monitor->{Enabled} ) { + Info('Motion detection not enabled on monitor '.$id); + return; + } my $trigger = $1; my $delay = $2; my $trigger_data; if ( $trigger eq 'on' ) { + if ( $score <= 0 ) { + Warning('Triggering on with invalid score will have no result.'); + return; + } zmTriggerEventOn($monitor, $score, $cause, $text); zmTriggerShowtext($monitor, $showtext) if defined($showtext); Info("Trigger '$trigger' '$cause'"); diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index b89f6a8f8..8226af37d 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -40,10 +40,11 @@ configuring upgrades etc, including on the fly upgrades. -c, --check - Check for updated versions of ZoneMinder -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. --v, --version= - Force upgrade to the current version from --u, --user= - Alternate DB user with privileges to alter DB --p, --pass= - Password of alternate DB user with privileges to alter DB --d,--dir= - Directory containing update files if not in default build location +-v , --version= - Force upgrade to the current version from +-u , --user= - Alternate DB user with privileges to alter DB +-p , --pass= - Password of alternate DB user with privileges to alter DB +-s, --super - Use system maintenance account on debian based systems instead of unprivileged account +-d , --dir= - Directory containing update files if not in default build location -interactive - interact with the user -nointeractive - do not interact with the user @@ -103,6 +104,7 @@ my $migrateEvents = 0; my $version = ''; my $dbUser = $Config{ZM_DB_USER}; my $dbPass = $Config{ZM_DB_PASS}; +my $super = 0; my $updateDir = ''; GetOptions( @@ -115,6 +117,7 @@ GetOptions( 'interactive!' =>\$interactive, 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, + 'super' =>\$super, 'dir:s' =>\$updateDir ) or pod2usage(-exitstatus => -1); @@ -406,6 +409,12 @@ if ( $version ) { if ( $response =~ /^[yY]$/ ) { my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $command = 'mysqldump'; + if ($super) { + $command .= ' --defaults-file=/etc/mysql/debian.cnf'; + } elsif ($dbUser) { + $command .= ' -u'.$dbUser; + $command .= ' -p\''.$dbPass.'\'' if $dbPass; + } if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $command .= ' -S'.$portOrSocket; @@ -415,10 +424,6 @@ if ( $version ) { } else { $command .= ' -h'.$host; } - if ( $dbUser ) { - $command .= ' -u'.$dbUser; - $command .= ' -p\''.$dbPass.'\'' if $dbPass; - } my $backup = '@ZM_TMPDIR@/'.$Config{ZM_DB_NAME}.'-'.$version.'.dump'; $command .= ' --add-drop-table --databases '.$Config{ZM_DB_NAME}.' > '.$backup; print("Creating backup to $backup. This may take several minutes.\n"); @@ -1001,6 +1006,12 @@ sub patchDB { my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ) if $Config{ZM_DB_HOST}; my $command = 'mysql'; + if ($super) { + $command .= ' --defaults-file=/etc/mysql/debian.cnf'; + } elsif ($dbUser) { + $command .= ' -u'.$dbUser; + $command .= ' -p\''.$dbPass.'\'' if $dbPass; + } if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { $command .= ' -S'.$portOrSocket; @@ -1010,10 +1021,6 @@ sub patchDB { } elsif ( $host ) { $command .= ' -h'.$host; } - if ( $dbUser ) { - $command .= ' -u'.$dbUser; - $command .= ' -p\''.$dbPass.'\'' if $dbPass; - } $command .= ' '.$Config{ZM_DB_NAME}.' < '; if ( $updateDir ) { $command .= $updateDir; diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index 04cfabb04..c676eb164 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -201,7 +201,7 @@ if ( $event_id ) { my $sql = " SELECT (SELECT max(Delta) FROM Frames WHERE EventId=Events.Id)-(SELECT min(Delta) FROM Frames WHERE EventId=Events.Id) as FullLength, Events.*, - unix_timestamp(Events.StartTime) as Time, + unix_timestamp(Events.StartDateTime) as Time, M.Name as MonitorName, M.Palette FROM Events diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 1302039ee..f1fbd16b4 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -68,6 +68,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); +my $zm_terminate = 0; +sub TermHandler { + Info('Received TERM, exiting'); + $zm_terminate = 1; +} +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; Info('Watchdog starting, pausing for '.START_DELAY.' seconds'); sleep(START_DELAY); @@ -77,7 +84,7 @@ my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'S my $sth = $dbh->prepare_cached($sql) or Fatal("Can't prepare '$sql': ".$dbh->errstr()); -while( 1 ) { +while (!$zm_terminate) { while ( ! ( $dbh and $dbh->ping() ) ) { if ( ! ( $dbh = zmDbConnect() ) ) { sleep($Config{ZM_WATCH_CHECK_INTERVAL}); @@ -123,7 +130,7 @@ while( 1 ) { ) ? (3/$monitor->{MaxFPS}) : $Config{ZM_WATCH_MAX_DELAY} ; - my $image_delay = $now-$capture_time; + my $image_delay = $now - $capture_time; Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay"); if ( $image_delay > $max_image_delay ) { Warning("Restarting capture daemon for " @@ -138,9 +145,6 @@ while( 1 ) { } if ( $restart ) { - # Because zma depends on zmc, and zma can hold the shm in place, preventing zmc from using the space in /dev/shm, - # we need to stop zma before restarting zmc. - runCommand("zmdc.pl stop zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; my $command; if ( $monitor->{Type} eq 'Local' ) { $command = "zmdc.pl restart zmc -d $monitor->{Device}"; @@ -148,7 +152,6 @@ while( 1 ) { $command = "zmdc.pl restart zmc -m $monitor->{Id}"; } runCommand($command); - runCommand("zmdc.pl start zma -m $$monitor{Id}") if $monitor->{Function} ne 'Monitor'; } elsif ( $monitor->{Function} ne 'Monitor' ) { # Now check analysis daemon $restart = 0; @@ -160,7 +163,7 @@ while( 1 ) { Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); } elsif ( !$image_time ) { # We can't get the last capture time so can't be sure it's died. - $restart = 1; + #$restart = 1; Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { @@ -181,8 +184,13 @@ while( 1 ) { } if ( $restart ) { - Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}"); - my $command = 'zmdc.pl restart zma -m '.$monitor->{Id}; + Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); + my $command; + if ( $monitor->{Type} eq 'Local' ) { + $command = "zmdc.pl restart zmc -d $monitor->{Device}"; + } else { + $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + } runCommand($command); } # end if restart } # end if check analysis daemon @@ -191,7 +199,7 @@ while( 1 ) { } # end foreach monitor sleep($Config{ZM_WATCH_CHECK_INTERVAL}); -} # end while (1) +} # end while (!$zm_terminate) Info("Watchdog exiting"); exit(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf7bf0bc5..281c7ba34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,37 +1,141 @@ # CMakeLists.txt for the ZoneMinder binaries # Create files from the .in files -configure_file(zm_config_data.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config_data.h" @ONLY) - -# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_libvnc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp zm_fifo.cpp zm_crypt.cpp) +configure_file(zm_config_data.h.in "${CMAKE_BINARY_DIR}/zm_config_data.h" @ONLY) +# Group together all the source files that are used by all the binaries (zmc, zmu, zms etc) +set(ZM_BIN_SRC_FILES + zm_analysis_thread.cpp + zm_box.cpp + zm_buffer.cpp + zm_camera.cpp + zm_comms.cpp + zm_config.cpp + zm_coord.cpp + zm_curl_camera.cpp + zm_crypt.cpp + zm.cpp + zm_db.cpp + zm_decoder_thread.cpp + zm_logger.cpp + zm_event.cpp + zm_eventstream.cpp + zm_exception.cpp + zm_fifo.cpp + zm_fifo_debug.cpp + zm_fifo_stream.cpp + zm_file_camera.cpp + zm_font.cpp + zm_frame.cpp + zm_group.cpp + zm_image.cpp + zm_jpeg.cpp + zm_libvlc_camera.cpp + zm_libvnc_camera.cpp + zm_local_camera.cpp + zm_monitor.cpp + zm_monitorstream.cpp + zm_ffmpeg.cpp + zm_ffmpeg_camera.cpp + zm_ffmpeg_input.cpp + zm_mpeg.cpp + zm_packet.cpp + zm_packetqueue.cpp + zm_poly.cpp + zm_regexp.cpp + zm_remote_camera.cpp + zm_remote_camera_http.cpp + zm_remote_camera_nvsocket.cpp + zm_remote_camera_rtsp.cpp + zm_rtp.cpp + zm_rtp_ctrl.cpp + zm_rtp_data.cpp + zm_rtp_source.cpp + zm_rtsp.cpp + zm_rtsp_auth.cpp + zm_rtsp_server_fifo_source.cpp + zm_rtsp_server_fifo_adts_source.cpp + zm_rtsp_server_fifo_h264_source.cpp + zm_rtsp_server_fifo_audio_source.cpp + zm_rtsp_server_fifo_video_source.cpp + zm_sdp.cpp + zm_signal.cpp + zm_stream.cpp + zm_swscale.cpp + zm_time.cpp + zm_user.cpp + zm_utils.cpp + zm_videostore.cpp + zm_zone.cpp + zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) -link_directories(libbcrypt) + +target_include_directories(zm + PUBLIC + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(zm + PUBLIC + libbcrypt::bcrypt + jwt-cpp::jwt-cpp + RtspServer::RtspServer + martinmoene::span-lite + PRIVATE + zm-core-interface) add_executable(zmc zmc.cpp) -add_executable(zma zma.cpp) -add_executable(zmu zmu.cpp) add_executable(zms zms.cpp) +add_executable(zmu zmu.cpp) -# JWT is a header only library. -include_directories(libbcrypt/include/bcrypt) -include_directories(jwt-cpp/include/jwt-cpp) +target_link_libraries(zmc + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${ZM_BIN_LIBS} + ${CMAKE_DL_LIBS}) -target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) -target_link_libraries(zma zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) -target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) -target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS} bcrypt) +target_link_libraries(zms + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${ZM_BIN_LIBS} + ${CMAKE_DL_LIBS}) + +target_link_libraries(zmu + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${ZM_BIN_LIBS} + ${CMAKE_DL_LIBS} + bcrypt) # Generate man files for the binaries destined for the bin folder -FOREACH(CBINARY zma zmc zmu) +if(BUILD_MAN) + foreach(CBINARY zmc zmu) POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp ${CBINARY} 8 ${ZM_MANPAGE_DEST_PREFIX}) -ENDFOREACH(CBINARY zma zmc zmu) + endforeach(CBINARY zmc zmu) +endif() -install(TARGETS zmc zma zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(TARGETS zmc zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) +install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}") +if(HAVE_RTSP_SERVER) + add_executable(zm_rtsp_server zm_rtsp_server.cpp) + target_link_libraries(zm_rtsp_server + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} + ${ZM_BIN_LIBS} + ${CMAKE_DL_LIBS} + bcrypt) + install(TARGETS zm_rtsp_server RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() diff --git a/src/jwt-cpp/BaseTest.cpp b/src/jwt-cpp/BaseTest.cpp deleted file mode 100644 index aceeb38d4..000000000 --- a/src/jwt-cpp/BaseTest.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include "include/jwt-cpp/base.h" - -TEST(BaseTest, Base64Decode) { - ASSERT_EQ("1", jwt::base::decode("MQ==")); - ASSERT_EQ("12", jwt::base::decode("MTI=")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA==")); -} - -TEST(BaseTest, Base64DecodeURL) { - ASSERT_EQ("1", jwt::base::decode("MQ%3d%3d")); - ASSERT_EQ("12", jwt::base::decode("MTI%3d")); - ASSERT_EQ("123", jwt::base::decode("MTIz")); - ASSERT_EQ("1234", jwt::base::decode("MTIzNA%3d%3d")); -} - -TEST(BaseTest, Base64Encode) { - ASSERT_EQ("MQ==", jwt::base::encode("1")); - ASSERT_EQ("MTI=", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA==", jwt::base::encode("1234")); -} - -TEST(BaseTest, Base64EncodeURL) { - ASSERT_EQ("MQ%3d%3d", jwt::base::encode("1")); - ASSERT_EQ("MTI%3d", jwt::base::encode("12")); - ASSERT_EQ("MTIz", jwt::base::encode("123")); - ASSERT_EQ("MTIzNA%3d%3d", jwt::base::encode("1234")); -} \ No newline at end of file diff --git a/src/jwt-cpp/ClaimTest.cpp b/src/jwt-cpp/ClaimTest.cpp deleted file mode 100644 index 749edf2ef..000000000 --- a/src/jwt-cpp/ClaimTest.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -TEST(ClaimTest, AudienceAsString) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_FALSE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_TRUE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWT", decoded.get_type()); - auto aud = decoded.get_audience(); - ASSERT_EQ(1, aud.size()); - ASSERT_EQ("test", *aud.begin()); -} - -TEST(ClaimTest, SetAudienceAsString) { - auto token = jwt::create() - .set_type("JWT") - .set_audience("test") - .sign(jwt::algorithm::hs256("test")); - ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.ny5Fa0vzAg7tNL95KWg_ecBNd3XP3tdAzq0SFA6diY4", token); -} diff --git a/src/jwt-cpp/HelperTest.cpp b/src/jwt-cpp/HelperTest.cpp deleted file mode 100644 index f998e1289..000000000 --- a/src/jwt-cpp/HelperTest.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -namespace { - extern std::string google_cert; - extern std::string google_cert_key; -} - -TEST(HelperTest, Cert2Pubkey) { - auto key = jwt::helper::extract_pubkey_from_cert(google_cert); - ASSERT_EQ(google_cert_key, key); -} - -namespace { - std::string google_cert = R"(-----BEGIN CERTIFICATE----- -MIIF8DCCBVmgAwIBAgIKYFOB9QABAACIvTANBgkqhkiG9w0BAQUFADBGMQswCQYD -VQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZR29vZ2xlIElu -dGVybmV0IEF1dGhvcml0eTAeFw0xMzA1MjIxNTQ5MDRaFw0xMzEwMzEyMzU5NTla -MGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1N -b3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRUwEwYDVQQDFAwqLmdv -b2dsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARmSpIUbCqhUBq1UwnR -Ai7/TNSk6W8JmasR+I0r/NLDYv5yApbAz8HXXN8hDdurMRP6Jy1Q0UIKmyls8HPH -exoCo4IECjCCBAYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAsGA1Ud -DwQEAwIHgDAdBgNVHQ4EFgQUU3jT0NVNRgU5ZinRHGrlyoGEnoYwHwYDVR0jBBgw -FoAUv8Aw6/VDET5nup6R+/xq2uNrEiQwWwYDVR0fBFQwUjBQoE6gTIZKaHR0cDov -L3d3dy5nc3RhdGljLmNvbS9Hb29nbGVJbnRlcm5ldEF1dGhvcml0eS9Hb29nbGVJ -bnRlcm5ldEF1dGhvcml0eS5jcmwwZgYIKwYBBQUHAQEEWjBYMFYGCCsGAQUFBzAC -hkpodHRwOi8vd3d3LmdzdGF0aWMuY29tL0dvb2dsZUludGVybmV0QXV0aG9yaXR5 -L0dvb2dsZUludGVybmV0QXV0aG9yaXR5LmNydDAMBgNVHRMBAf8EAjAAMIICwwYD -VR0RBIICujCCAraCDCouZ29vZ2xlLmNvbYINKi5hbmRyb2lkLmNvbYIWKi5hcHBl -bmdpbmUuZ29vZ2xlLmNvbYISKi5jbG91ZC5nb29nbGUuY29tghYqLmdvb2dsZS1h -bmFseXRpY3MuY29tggsqLmdvb2dsZS5jYYILKi5nb29nbGUuY2yCDiouZ29vZ2xl -LmNvLmlugg4qLmdvb2dsZS5jby5qcIIOKi5nb29nbGUuY28udWuCDyouZ29vZ2xl -LmNvbS5hcoIPKi5nb29nbGUuY29tLmF1gg8qLmdvb2dsZS5jb20uYnKCDyouZ29v -Z2xlLmNvbS5jb4IPKi5nb29nbGUuY29tLm14gg8qLmdvb2dsZS5jb20udHKCDyou -Z29vZ2xlLmNvbS52boILKi5nb29nbGUuZGWCCyouZ29vZ2xlLmVzggsqLmdvb2ds -ZS5mcoILKi5nb29nbGUuaHWCCyouZ29vZ2xlLml0ggsqLmdvb2dsZS5ubIILKi5n -b29nbGUucGyCCyouZ29vZ2xlLnB0gg8qLmdvb2dsZWFwaXMuY26CFCouZ29vZ2xl -Y29tbWVyY2UuY29tgg0qLmdzdGF0aWMuY29tggwqLnVyY2hpbi5jb22CECoudXJs -Lmdvb2dsZS5jb22CFioueW91dHViZS1ub2Nvb2tpZS5jb22CDSoueW91dHViZS5j -b22CFioueW91dHViZWVkdWNhdGlvbi5jb22CCyoueXRpbWcuY29tggthbmRyb2lk -LmNvbYIEZy5jb4IGZ29vLmdsghRnb29nbGUtYW5hbHl0aWNzLmNvbYIKZ29vZ2xl -LmNvbYISZ29vZ2xlY29tbWVyY2UuY29tggp1cmNoaW4uY29tggh5b3V0dS5iZYIL -eW91dHViZS5jb22CFHlvdXR1YmVlZHVjYXRpb24uY29tMA0GCSqGSIb3DQEBBQUA -A4GBAAMn0K3j3yhC+X+uyh6eABa2Eq7xiY5/mUB886Ir19vxluSMNKD6n/iY8vHj -trn0BhuW8/vmJyudFkIcEDUYE4ivQMlsfIL7SOGw6OevVLmm02aiRHWj5T20Ds+S -OpueYUG3NBcHP/5IzhUYIQJbGzlQaUaZBMaQeC8ZslMNLWI2 ------END CERTIFICATE-----)"; - - std::string google_cert_key = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZkqSFGwqoVAatVMJ0QIu/0zUpOlv -CZmrEfiNK/zSw2L+cgKWwM/B11zfIQ3bqzET+ictUNFCCpspbPBzx3saAg== ------END PUBLIC KEY----- -)"; -} \ No newline at end of file diff --git a/src/jwt-cpp/README.md b/src/jwt-cpp/README.md deleted file mode 100644 index 7636f9f67..000000000 --- a/src/jwt-cpp/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# jwt-cpp - -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5f7055e294744901991fd0a1620b231d)](https://app.codacy.com/app/Thalhammer/jwt-cpp?utm_source=github.com&utm_medium=referral&utm_content=Thalhammer/jwt-cpp&utm_campaign=Badge_Grade_Settings) - -A header only library for creating and validating json web tokens in c++. - -## Signature algorithms -As of version 0.2.0 jwt-cpp supports all algorithms defined by the spec. The modular design of jwt-cpp allows one to add additional algorithms without any problems. If you need any feel free to open a pull request. -For the sake of completeness, here is a list of all supported algorithms: -* HS256 -* HS384 -* HS512 -* RS256 -* RS384 -* RS512 -* ES256 -* ES384 -* ES512 -* PS256 -* PS384 -* PS512 - -## Examples -Simple example of decoding a token and printing all claims: -```c++ -#include -#include - -int main(int argc, const char** argv) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - auto decoded = jwt::decode(token); - - for(auto& e : decoded.get_payload_claims()) - std::cout << e.first << " = " << e.second.to_json() << std::endl; -} -``` - -In order to verify a token you first build a verifier and use it to verify a decoded token. -```c++ -auto verifier = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{ "secret" }) - .with_issuer("auth0"); - -verifier.verify(decoded_token); -``` -The created verifier is stateless so you can reuse it for different tokens. - -Creating a token (and signing) is equally easy. -```c++ -auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .set_payload_claim("sample", std::string("test")) - .sign(jwt::algorithm::hs256{"secret"}); -``` - -Here is a simple example of creating a token that will expire in 2 hours: - -```c++ - - // Note to @Thalhammer: please replace with a better example if this is not a good way - auto token = jwt::create() - .set_issuer("auth0") - .set_issued_at(jwt::date(std::chrono::system_clock::now())) - .set_expires_at(jwt::date(std::chrono::system_clock::now()+ std::chrono::seconds{3600})) - .sign(jwt::algorithm::hs256{"secret"} - - -``` - -## Contributing -If you have an improvement or found a bug feel free to [open an issue](https://github.com/Thalhammer/jwt-cpp/issues/new) or add the change and create a pull request. If you file a bug please make sure to include as much information about your environment (compiler version, etc.) as possible to help reproduce the issue. If you add a new feature please make sure to also include test cases for it. - -## Dependencies -In order to use jwt-cpp you need the following tools. -* libcrypto (openssl or compatible) -* libssl-dev (for the header files) -* a compiler supporting at least c++11 -* basic stl support - -In order to build the test cases you also need -* gtest installed in linker path -* pthread - -## Troubleshooting -#### Expired tokens -If you are generating tokens that seem to immediately expire, you are likely not using UTC. Specifically, -if you use `get_time` to get the current time, it likely uses localtime, while this library uses UTC, which may be why your token is immediately expiring. Please see example above on the right way to use current time. - -#### Missing _HMAC amd _EVP_sha256 symbols on Mac -There seems to exists a problem with the included openssl library of MacOS. Make sure you link to one provided by brew. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/6) for more details. -#### Building on windows fails with syntax errors -The header "Windows.h", which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. -See [here](https://github.com/Thalhammer/jwt-cpp/issues/5) for more details. To fix this do one of the following things: -* define NOMINMAX, which suppresses this behaviour -* include this library before you include windows.h -* place ```#undef max``` and ```#undef min``` before you include this library diff --git a/src/jwt-cpp/TestMain.cpp b/src/jwt-cpp/TestMain.cpp deleted file mode 100644 index b8b7262bc..000000000 --- a/src/jwt-cpp/TestMain.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main(int argc, char *argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/src/jwt-cpp/TokenFormatTest.cpp b/src/jwt-cpp/TokenFormatTest.cpp deleted file mode 100644 index e670a82c8..000000000 --- a/src/jwt-cpp/TokenFormatTest.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -TEST(TokenFormatTest, MissingDot) { - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9"), std::invalid_argument); - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0eyJpc3MiOiJhdXRoMCJ9."), std::invalid_argument); -} - -TEST(TokenFormatTest, InvalidChar) { - ASSERT_THROW(jwt::decode("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0().eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); -} - -TEST(TokenFormatTest, InvalidJSON) { - ASSERT_THROW(jwt::decode("YXsiYWxnIjoibm9uZSIsInR5cCI6IkpXUyJ9YQ.eyJpc3MiOiJhdXRoMCJ9."), std::runtime_error); -} \ No newline at end of file diff --git a/src/jwt-cpp/TokenTest.cpp b/src/jwt-cpp/TokenTest.cpp deleted file mode 100644 index 6d2d004c3..000000000 --- a/src/jwt-cpp/TokenTest.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include -#include "include/jwt-cpp/jwt.h" - -namespace { - extern std::string rsa_priv_key; - extern std::string rsa_pub_key; - extern std::string rsa_pub_key_invalid; - extern std::string rsa512_priv_key; - extern std::string rsa512_pub_key; - extern std::string rsa512_pub_key_invalid; - extern std::string ecdsa_priv_key; - extern std::string ecdsa_pub_key; - extern std::string ecdsa_pub_key_invalid; -} - -TEST(TokenTest, DecodeToken) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - auto decoded = jwt::decode(token); - - ASSERT_TRUE(decoded.has_algorithm()); - ASSERT_TRUE(decoded.has_type()); - ASSERT_FALSE(decoded.has_content_type()); - ASSERT_FALSE(decoded.has_key_id()); - ASSERT_TRUE(decoded.has_issuer()); - ASSERT_FALSE(decoded.has_subject()); - ASSERT_FALSE(decoded.has_audience()); - ASSERT_FALSE(decoded.has_expires_at()); - ASSERT_FALSE(decoded.has_not_before()); - ASSERT_FALSE(decoded.has_issued_at()); - ASSERT_FALSE(decoded.has_id()); - - ASSERT_EQ("HS256", decoded.get_algorithm()); - ASSERT_EQ("JWS", decoded.get_type()); - ASSERT_EQ("auth0", decoded.get_issuer()); -} - -TEST(TokenTest, CreateToken) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::none{}); - ASSERT_EQ("eyJhbGciOiJub25lIiwidHlwIjoiSldTIn0.eyJpc3MiOiJhdXRoMCJ9.", token); -} - -TEST(TokenTest, CreateTokenHS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::hs256{"secret"}); - ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE", token); -} - -TEST(TokenTest, CreateTokenRS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")); - - ASSERT_EQ( - "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ", token); -} - -TEST(TokenTest, CreateTokenRS512) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")); - - ASSERT_EQ( - "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZSSQyLKvI0" - "TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4GB" - "hfGgejPBCBlGrQtqFGFdHHOjNHY", token); -} - -TEST(TokenTest, CreateTokenPS256) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenPS384) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps384(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenPS512) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::ps512(rsa_pub_key, rsa_priv_key, "", "")); - - // TODO: Find a better way to check if generated signature is valid - // Can't do simple check for equal since pss adds random salt. -} - -TEST(TokenTest, CreateTokenES256) { - - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::es256("", ecdsa_priv_key, "", "")); - - auto decoded = jwt::decode(token); - - ASSERT_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", "")).verify(decoded), jwt::signature_verification_exception); - ASSERT_NO_THROW(jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")).verify(decoded)); -} - -TEST(TokenTest, CreateTokenES256NoPrivate) { - - ASSERT_THROW([](){ - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")); - }(), jwt::signature_generation_exception); -} - -TEST(TokenTest, VerifyTokenRS256) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS256PublicOnly) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS256Fail) { - std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.VA2i1ui1cnoD6I3wnji1WAVCf29EekysvevGrT2GXqK1dDMc8" - "HAZCTQxa1Q8NppnpYV-hlqxh-X3Bb0JOePTGzjynpNZoJh2aHZD-GKpZt7OO1Zp8AFWPZ3p8Cahq8536fD8RiBES9jRsvChZvOqA7gMcFc4" - "YD0iZhNIcI7a654u5yPYyTlf5kjR97prCf_OXWRn-bYY74zna4p_bP9oWCL4BkaoRcMxi-IR7kmVcCnvbYqyIrKloXP2qPO442RBGqU7Ov9" - "sGQxiVqtRHKXZR9RbfvjrErY1KGiCp9M5i2bsUHadZEY44FE2jiOmx-uc2z5c05CCXqVSpfCjWbh9gQ"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs256(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenRS512) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, rsa512_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS512PublicOnly) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa512_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenRS512Fail) { - std::string token = "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.GZhnjtsvBl2_KDSxg4JW6xnmNjr2mWhYSZ" - "SSQyLKvI0TK86sJKchkt_HDy2IC5l5BGRhq_Xv9pHdA1umidQZG3a7gWvHsujqybCBgBraMTd1wJrCl4QxFg2RYHhHbRqb9BnPJgFD_vryd4" - "GBhfGgejPBCBlGrQtqFGFdHHOjNHY"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::rs512(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenHS256) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{ "secret" }) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyFail) { - auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .sign(jwt::algorithm::none{}); - - auto decoded_token = jwt::decode(token); - - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth"); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_audience({ "test" }); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_subject("test"); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } - { - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::none{}) - .with_issuer("auth0") - .with_claim("myclaim", jwt::claim(std::string("test"))); - ASSERT_THROW(verify.verify(decoded_token), jwt::token_verification_exception); - } -} - -TEST(TokenTest, VerifyTokenES256) { - const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; - - auto verify = jwt::verify().allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key, "", "", "")); - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenES256Fail) { - const std::string token = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.4iVk3-Y0v4RT4_9IaQlp-8dZ_4fsTzIylgrPTDLrEvTHBTyVS3tgPbr2_IZfLETtiKRqCg0aQ5sh9eIsTTwB1g"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::es256(ecdsa_pub_key_invalid, "", "", "")); - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -TEST(TokenTest, VerifyTokenPS256) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenPS256PublicOnly) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - verify.verify(decoded_token); -} - -TEST(TokenTest, VerifyTokenPS256Fail) { - std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE" - "4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-" - "N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis" - "HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA"; - - auto verify = jwt::verify() - .allow_algorithm(jwt::algorithm::ps256(rsa_pub_key_invalid, "", "", "")) - .with_issuer("auth0"); - - auto decoded_token = jwt::decode(token); - - ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception); -} - -namespace { - std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ -tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB -XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k -ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL -DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ -mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K -3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN -tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 -ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj -NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 -ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO -u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U -6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui -wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us -rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv -TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp -PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ -FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz -FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG -m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC -PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq -PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE -kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe -RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb -vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX -rK0/Ikt5ybqUzKCMJZg2VKGTxg== ------END PRIVATE KEY-----)"; - std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 -yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 -83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs -WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT -69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 -AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK -5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa -vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 -FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC -VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M -r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa512_priv_key = R"(-----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw -33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW -+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB -AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS -3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp -uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE -2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 -GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K -Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY -6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 -fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 -Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP -FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== ------END RSA PRIVATE KEY-----)"; - std::string rsa512_pub_key = R"(-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd -UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs -HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D -o2kQ+X5xK9cipRgEKwIDAQAB ------END PUBLIC KEY-----)"; - std::string rsa512_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzYuc22QSst/dS7geYYK -5l5kLxU0tayNdixkEQ17ix+CUcUbKIsnyftZxaCYT46rQtXgCaYRdJcbB3hmyrOa -vkhTpX79xJZnQmfuamMbZBqitvscxW9zRR9tBUL6vdi/0rpoUwPMEh8+Bw7CgYR0 -FK0DhWYBNDfe9HKcyZEv3max8Cdq18htxjEsdYO0iwzhtKRXomBWTdhD5ykd/fAC -VTr4+KEY+IeLvubHVmLUhbE5NgWXxrRpGasDqzKhCTmsa2Ysf712rl57SlH0Wz/M -r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s -YwIDAQAB ------END PUBLIC KEY-----)"; - std::string ecdsa_priv_key = R"(-----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPGJGAm4X1fvBuC1z -SpO/4Izx6PXfNMaiKaS5RUkFqEGhRANCAARCBvmeksd3QGTrVs2eMrrfa7CYF+sX -sjyGg+Bo5mPKGH4Gs8M7oIvoP9pb/I85tdebtKlmiCZHAZE5w4DfJSV6 ------END PRIVATE KEY-----)"; - std::string ecdsa_pub_key = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr -F7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg== ------END PUBLIC KEY-----)"; - std::string ecdsa_pub_key_invalid = R"(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT -CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== ------END PUBLIC KEY-----)"; -} diff --git a/src/jwt-cpp/include/jwt-cpp/base.h b/src/jwt-cpp/include/jwt-cpp/base.h deleted file mode 100644 index dfca7fc08..000000000 --- a/src/jwt-cpp/include/jwt-cpp/base.h +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once -#include -#include - -namespace jwt { - namespace alphabet { - struct base64 { - static const std::array& data() { - static std::array data = { - {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; - return data; - }; - static const std::string& fill() { - static std::string fill = "="; - return fill; - } - }; - struct base64url { - static const std::array& data() { - static std::array data = { - {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; - return data; - }; - static const std::string& fill() { - static std::string fill = "%3d"; - return fill; - } - }; - } - - class base { - public: - template - static std::string encode(const std::string& bin) { - return encode(bin, T::data(), T::fill()); - } - template - static std::string decode(const std::string& base) { - return decode(base, T::data(), T::fill()); - } - - private: - static std::string encode(const std::string& bin, const std::array& alphabet, const std::string& fill) { - size_t size = bin.size(); - std::string res; - - // clear incomplete bytes - size_t fast_size = size - size % 3; - for (size_t i = 0; i < fast_size;) { - uint32_t octet_a = (unsigned char)bin[i++]; - uint32_t octet_b = (unsigned char)bin[i++]; - uint32_t octet_c = (unsigned char)bin[i++]; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += alphabet[(triple >> 1 * 6) & 0x3F]; - res += alphabet[(triple >> 0 * 6) & 0x3F]; - } - - if (fast_size == size) - return res; - - size_t mod = size % 3; - - uint32_t octet_a = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_b = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - uint32_t octet_c = fast_size < size ? (unsigned char)bin[fast_size++] : 0; - - uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; - - switch (mod) { - case 1: - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += fill; - res += fill; - break; - case 2: - res += alphabet[(triple >> 3 * 6) & 0x3F]; - res += alphabet[(triple >> 2 * 6) & 0x3F]; - res += alphabet[(triple >> 1 * 6) & 0x3F]; - res += fill; - break; - default: - break; - } - - return res; - } - - static std::string decode(const std::string& base, const std::array& alphabet, const std::string& fill) { - size_t size = base.size(); - - size_t fill_cnt = 0; - while (size > fill.size()) { - if (base.substr(size - fill.size(), fill.size()) == fill) { - fill_cnt++; - size -= fill.size(); - if(fill_cnt > 2) - throw std::runtime_error("Invalid input"); - } - else break; - } - - if ((size + fill_cnt) % 4 != 0) - throw std::runtime_error("Invalid input"); - - size_t out_size = size / 4 * 3; - std::string res; - res.reserve(out_size); - - auto get_sextet = [&](size_t offset) { - for (size_t i = 0; i < alphabet.size(); i++) { - if (alphabet[i] == base[offset]) - return i; - } - throw std::runtime_error("Invalid input"); - }; - - - size_t fast_size = size - size % 4; - for (size_t i = 0; i < fast_size;) { - uint32_t sextet_a = get_sextet(i++); - uint32_t sextet_b = get_sextet(i++); - uint32_t sextet_c = get_sextet(i++); - uint32_t sextet_d = get_sextet(i++); - - uint32_t triple = (sextet_a << 3 * 6) - + (sextet_b << 2 * 6) - + (sextet_c << 1 * 6) - + (sextet_d << 0 * 6); - - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - res += (triple >> 0 * 8) & 0xFF; - } - - if (fill_cnt == 0) - return res; - - uint32_t triple = (get_sextet(fast_size) << 3 * 6) - + (get_sextet(fast_size + 1) << 2 * 6); - - switch (fill_cnt) { - case 1: - triple |= (get_sextet(fast_size + 2) << 1 * 6); - res += (triple >> 2 * 8) & 0xFF; - res += (triple >> 1 * 8) & 0xFF; - break; - case 2: - res += (triple >> 2 * 8) & 0xFF; - break; - default: - break; - } - - return res; - } - }; -} diff --git a/src/jwt-cpp/include/jwt-cpp/jwt_cpp.h b/src/jwt-cpp/include/jwt-cpp/jwt_cpp.h deleted file mode 100644 index c8c3c8719..000000000 --- a/src/jwt-cpp/include/jwt-cpp/jwt_cpp.h +++ /dev/null @@ -1,1593 +0,0 @@ -#pragma once -#define PICOJSON_USE_INT64 -#include "picojson.h" -#include "base.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//If openssl version less than 1.1 -#if OPENSSL_VERSION_NUMBER < 269484032 -#define OPENSSL10 -#endif - -#ifndef JWT_CLAIM_EXPLICIT -#define JWT_CLAIM_EXPLICIT 0 -#endif - -namespace jwt { - using date = std::chrono::system_clock::time_point; - - struct signature_verification_exception : public std::runtime_error { - signature_verification_exception() - : std::runtime_error("signature verification failed") - {} - explicit signature_verification_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit signature_verification_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct signature_generation_exception : public std::runtime_error { - signature_generation_exception() - : std::runtime_error("signature generation failed") - {} - explicit signature_generation_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit signature_generation_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct rsa_exception : public std::runtime_error { - explicit rsa_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit rsa_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct ecdsa_exception : public std::runtime_error { - explicit ecdsa_exception(const std::string& msg) - : std::runtime_error(msg) - {} - explicit ecdsa_exception(const char* msg) - : std::runtime_error(msg) - {} - }; - struct token_verification_exception : public std::runtime_error { - token_verification_exception() - : std::runtime_error("token verification failed") - {} - explicit token_verification_exception(const std::string& msg) - : std::runtime_error("token verification failed: " + msg) - {} - }; - - namespace helper { - inline - std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { - // TODO: Cannot find the exact version this change happended -#if OPENSSL_VERSION_NUMBER <= 0x1000114fL - std::unique_ptr certbio(BIO_new_mem_buf(const_cast(certstr.data()), certstr.size()), BIO_free_all); -#else - std::unique_ptr certbio(BIO_new_mem_buf(certstr.data(), certstr.size()), BIO_free_all); -#endif - std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); - - std::unique_ptr cert(PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); - if (!cert) throw rsa_exception("Error loading cert into memory"); - std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); - if(!key) throw rsa_exception("Error getting public key from certificate"); - if(!PEM_write_bio_PUBKEY(keybio.get(), key.get())) throw rsa_exception("Error writing public key data in PEM format"); - char* ptr = nullptr; - auto len = BIO_get_mem_data(keybio.get(), &ptr); - if(len <= 0 || ptr == nullptr) throw rsa_exception("Failed to convert pubkey to pem"); - std::string res(ptr, len); - return res; - } - } - - namespace algorithm { - /** - * "none" algorithm. - * - * Returns and empty signature and checks if the given signature is empty. - */ - struct none { - /// Return an empty string - std::string sign(const std::string&) const { - return ""; - } - /// Check if the given signature is empty. JWT's with "none" algorithm should not contain a signature. - void verify(const std::string&, const std::string& signature) const { - if (!signature.empty()) - throw signature_verification_exception(); - } - /// Get algorithm name - std::string name() const { - return "none"; - } - }; - /** - * Base class for HMAC family of algorithms - */ - struct hmacsha { - /** - * Construct new hmac algorithm - * \param key Key to use for HMAC - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - hmacsha(std::string key, const EVP_MD*(*md)(), const std::string& name) - : secret(std::move(key)), md(md), alg_name(name) - {} - /** - * Sign jwt data - * \param data The data to sign - * \return HMAC signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - std::string res; - res.resize(EVP_MAX_MD_SIZE); - unsigned int len = res.size(); - if (HMAC(md(), secret.data(), secret.size(), (const unsigned char*)data.data(), data.size(), (unsigned char*)res.data(), &len) == nullptr) - throw signature_generation_exception(); - res.resize(len); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - try { - auto res = sign(data); - bool matched = true; - for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) - if (res[i] != signature[i]) - matched = false; - if (res.size() != signature.size()) - matched = false; - if (!matched) - throw signature_verification_exception(); - } - catch (const signature_generation_exception&) { - throw signature_verification_exception(); - } - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /// HMAC secrect - const std::string secret; - /// HMAC hash generator - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - /** - * Base class for RSA family of algorithms - */ - struct rsa { - /** - * Construct new rsa algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } - pkey.reset(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed"); - - if (!private_key.empty()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw rsa_exception("failed to load private key: bio_write failed"); - RSA* privkey = PEM_read_bio_RSAPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()); - if (privkey == nullptr) - throw rsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - if (EVP_PKEY_assign_RSA(pkey.get(), privkey) == 0) { - RSA_free(privkey); - throw rsa_exception("failed to load private key: EVP_PKEY_assign_RSA failed"); - } - } - } - /** - * Sign jwt data - * \param data The data to sign - * \return RSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif - if (!ctx) - throw signature_generation_exception("failed to create signature: could not create context"); - if (!EVP_SignInit(ctx.get(), md())) - throw signature_generation_exception("failed to create signature: SignInit failed"); - - std::string res; - res.resize(EVP_PKEY_size(pkey.get())); - unsigned int len = 0; - - if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) - throw signature_generation_exception(); - if (!EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get())) - throw signature_generation_exception(); - - res.resize(len); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_create(), EVP_MD_CTX_free); -#endif - if (!ctx) - throw signature_verification_exception("failed to verify signature: could not create context"); - if (!EVP_VerifyInit(ctx.get(), md())) - throw signature_verification_exception("failed to verify signature: VerifyInit failed"); - if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) - throw signature_verification_exception("failed to verify signature: VerifyUpdate failed"); - if (!EVP_VerifyFinal(ctx.get(), (const unsigned char*)signature.data(), signature.size(), pkey.get())) - throw signature_verification_exception(); - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /// OpenSSL structure containing converted keys - std::shared_ptr pkey; - /// Hash generator - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - /** - * Base class for ECDSA family of algorithms - */ - struct ecdsa { - /** - * Construct new ecdsa algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - if (private_key.empty()) { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw ecdsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw ecdsa_exception("failed to load public key: bio_write failed"); - } - - pkey.reset(PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EC_KEY_free); - if (!pkey) - throw ecdsa_exception("failed to load public key: PEM_read_bio_EC_PUBKEY failed"); - } else { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw ecdsa_exception("failed to load private key: bio_write failed"); - pkey.reset(PEM_read_bio_ECPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()), EC_KEY_free); - if (!pkey) - throw ecdsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - } - - if(EC_KEY_check_key(pkey.get()) == 0) - throw ecdsa_exception("failed to load key: key is invalid"); - } - /** - * Sign jwt data - * \param data The data to sign - * \return ECDSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - const std::string hash = generate_hash(data); - - std::unique_ptr - sig(ECDSA_do_sign((const unsigned char*)hash.data(), hash.size(), pkey.get()), ECDSA_SIG_free); - if(!sig) - throw signature_generation_exception(); -#ifdef OPENSSL10 - - return bn2raw(sig->r) + bn2raw(sig->s); -#else - const BIGNUM *r; - const BIGNUM *s; - ECDSA_SIG_get0(sig.get(), &r, &s); - return bn2raw(r) + bn2raw(s); -#endif - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - const std::string hash = generate_hash(data); - auto r = raw2bn(signature.substr(0, signature.size() / 2)); - auto s = raw2bn(signature.substr(signature.size() / 2)); - -#ifdef OPENSSL10 - ECDSA_SIG sig; - sig.r = r.get(); - sig.s = s.get(); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), &sig, pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); -#else - ECDSA_SIG *sig = ECDSA_SIG_new(); - - ECDSA_SIG_set0(sig, r.get(), s.get()); - - if(ECDSA_do_verify((const unsigned char*)hash.data(), hash.size(), sig, pkey.get()) != 1) - throw signature_verification_exception("Invalid signature"); -#endif - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /** - * Convert a OpenSSL BIGNUM to a std::string - * \param bn BIGNUM to convert - * \return bignum as string - */ -#ifdef OPENSSL10 - static std::string bn2raw(BIGNUM* bn) -#else - static std::string bn2raw(const BIGNUM* bn) -#endif - { - std::string res; - res.resize(BN_num_bytes(bn)); - BN_bn2bin(bn, (unsigned char*)res.data()); - if(res.size()%2 == 1 && res[0] == 0x00) - return res.substr(1); - return res; - } - /** - * Convert an std::string to a OpenSSL BIGNUM - * \param raw String to convert - * \return BIGNUM representation - */ - static std::unique_ptr raw2bn(const std::string& raw) { - if(static_cast(raw[0]) >= 0x80) { - std::string str(1, 0x00); - str += raw; - return std::unique_ptr(BN_bin2bn((const unsigned char*)str.data(), str.size(), nullptr), BN_free); - } - return std::unique_ptr(BN_bin2bn((const unsigned char*)raw.data(), raw.size(), nullptr), BN_free); - } - - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); -#endif - if(EVP_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; - } - - /// OpenSSL struct containing keys - std::shared_ptr pkey; - /// Hash generator function - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - - /** - * Base class for PSS-RSA family of algorithms - */ - struct pss { - /** - * Construct new pss algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - * \param md Pointer to hash function - * \param name Name of the algorithm - */ - pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name) - : md(md), alg_name(name) - { - std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if(public_key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { - auto pkey = helper::extract_pubkey_from_cert(public_key, public_key_password); - if ((size_t)BIO_write(pubkey_bio.get(), pkey.data(), pkey.size()) != pkey.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } else { - if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) - throw rsa_exception("failed to load public key: bio_write failed"); - } - pkey.reset(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EVP_PKEY_free); - if (!pkey) - throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed"); - - if (!private_key.empty()) { - std::unique_ptr privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); - if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size()) - throw rsa_exception("failed to load private key: bio_write failed"); - RSA* privkey = PEM_read_bio_RSAPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str()); - if (privkey == nullptr) - throw rsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed"); - if (EVP_PKEY_assign_RSA(pkey.get(), privkey) == 0) { - RSA_free(privkey); - throw rsa_exception("failed to load private key: EVP_PKEY_assign_RSA failed"); - } - } - } - /** - * Sign jwt data - * \param data The data to sign - * \return ECDSA signature for the given data - * \throws signature_generation_exception - */ - std::string sign(const std::string& data) const { - auto hash = this->generate_hash(data); - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - const int size = RSA_size(key.get()); - - std::string padded(size, 0x00); - if (!RSA_padding_add_PKCS1_PSS_mgf1(key.get(), (unsigned char*)padded.data(), (const unsigned char*)hash.data(), md(), md(), -1)) - throw signature_generation_exception("failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed"); - - std::string res(size, 0x00); - if (RSA_private_encrypt(size, (const unsigned char*)padded.data(), (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < 0) - throw signature_generation_exception("failed to create signature: RSA_private_encrypt failed"); - return res; - } - /** - * Check if signature is valid - * \param data The data to check signature against - * \param signature Signature provided by the jwt - * \throws signature_verification_exception If the provided signature does not match - */ - void verify(const std::string& data, const std::string& signature) const { - auto hash = this->generate_hash(data); - - std::unique_ptr key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free); - const int size = RSA_size(key.get()); - - std::string sig(size, 0x00); - if(!RSA_public_decrypt(signature.size(), (const unsigned char*)signature.data(), (unsigned char*)sig.data(), key.get(), RSA_NO_PADDING)) - throw signature_verification_exception("Invalid signature"); - - if(!RSA_verify_PKCS1_PSS_mgf1(key.get(), (const unsigned char*)hash.data(), md(), md(), (const unsigned char*)sig.data(), -1)) - throw signature_verification_exception("Invalid signature"); - } - /** - * Returns the algorithm name provided to the constructor - * \return Algorithmname - */ - std::string name() const { - return alg_name; - } - private: - /** - * Hash the provided data using the hash function specified in constructor - * \param data Data to hash - * \return Hash of data - */ - std::string generate_hash(const std::string& data) const { -#ifdef OPENSSL10 - std::unique_ptr ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); -#else - std::unique_ptr ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free); -#endif - if(EVP_DigestInit(ctx.get(), md()) == 0) - throw signature_generation_exception("EVP_DigestInit failed"); - if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0) - throw signature_generation_exception("EVP_DigestUpdate failed"); - unsigned int len = 0; - std::string res; - res.resize(EVP_MD_CTX_size(ctx.get())); - if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0) - throw signature_generation_exception("EVP_DigestFinal failed"); - res.resize(len); - return res; - } - - /// OpenSSL structure containing keys - std::shared_ptr pkey; - /// Hash generator function - const EVP_MD*(*md)(); - /// Algorithmname - const std::string alg_name; - }; - - /** - * HS256 algorithm - */ - struct hs256 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs256(std::string key) - : hmacsha(std::move(key), EVP_sha256, "HS256") - {} - }; - /** - * HS384 algorithm - */ - struct hs384 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs384(std::string key) - : hmacsha(std::move(key), EVP_sha384, "HS384") - {} - }; - /** - * HS512 algorithm - */ - struct hs512 : public hmacsha { - /** - * Construct new instance of algorithm - * \param key HMAC signing key - */ - explicit hs512(std::string key) - : hmacsha(std::move(key), EVP_sha512, "HS512") - {} - }; - /** - * RS256 algorithm - */ - struct rs256 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") - {} - }; - /** - * RS384 algorithm - */ - struct rs384 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") - {} - }; - /** - * RS512 algorithm - */ - struct rs512 : public rsa { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - rs512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") - {} - }; - /** - * ES256 algorithm - */ - struct es256 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256") - {} - }; - /** - * ES384 algorithm - */ - struct es384 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384") - {} - }; - /** - * ES512 algorithm - */ - struct es512 : public ecdsa { - /** - * Construct new instance of algorithm - * \param public_key ECDSA public key in PEM format - * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - es512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512") - {} - }; - - /** - * PS256 algorithm - */ - struct ps256 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") - {} - }; - /** - * PS384 algorithm - */ - struct ps384 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") - {} - }; - /** - * PS512 algorithm - */ - struct ps512 : public pss { - /** - * Construct new instance of algorithm - * \param public_key RSA public key in PEM format - * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. - * \param public_key_password Password to decrypt public key pem. - * \param privat_key_password Password to decrypt private key pem. - */ - ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") - : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") - {} - }; - } - - /** - * Convenience wrapper for JSON value - */ - class claim { - picojson::value val; - public: - enum class type { - null, - boolean, - number, - string, - array, - object, - int64 - }; - - claim() - : val() - {} -#if JWT_CLAIM_EXPLICIT - explicit claim(std::string s) - : val(std::move(s)) - {} - explicit claim(const date& s) - : val(int64_t(std::chrono::system_clock::to_time_t(s))) - {} - explicit claim(const std::set& s) - : val(picojson::array(s.cbegin(), s.cend())) - {} - explicit claim(const picojson::value& val) - : val(val) - {} -#else - claim(std::string s) - : val(std::move(s)) - {} - claim(const date& s) - : val(int64_t(std::chrono::system_clock::to_time_t(s))) - {} - claim(const std::set& s) - : val(picojson::array(s.cbegin(), s.cend())) - {} - claim(const picojson::value& val) - : val(val) - {} -#endif - - /** - * Get wrapped json object - * \return Wrapped json object - */ - picojson::value to_json() const { - return val; - } - - /** - * Get type of contained object - * \return Type - * \throws std::logic_error An internal error occured - */ - type get_type() const { - if (val.is()) return type::null; - else if (val.is()) return type::boolean; - else if (val.is()) return type::int64; - else if (val.is()) return type::number; - else if (val.is()) return type::string; - else if (val.is()) return type::array; - else if (val.is()) return type::object; - else throw std::logic_error("internal error"); - } - - /** - * Get the contained object as a string - * \return content as string - * \throws std::bad_cast Content was not a string - */ - const std::string& as_string() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a date - * \return content as date - * \throws std::bad_cast Content was not a date - */ - date as_date() const { - return std::chrono::system_clock::from_time_t(as_int()); - } - /** - * Get the contained object as an array - * \return content as array - * \throws std::bad_cast Content was not an array - */ - const picojson::array& as_array() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a set of strings - * \return content as set of strings - * \throws std::bad_cast Content was not a set - */ - const std::set as_set() const { - std::set res; - for(auto& e : as_array()) { - if(!e.is()) - throw std::bad_cast(); - res.insert(e.get()); - } - return res; - } - /** - * Get the contained object as an integer - * \return content as int - * \throws std::bad_cast Content was not an int - */ - int64_t as_int() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a bool - * \return content as bool - * \throws std::bad_cast Content was not a bool - */ - bool as_bool() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - /** - * Get the contained object as a number - * \return content as double - * \throws std::bad_cast Content was not a number - */ - double as_number() const { - if (!val.is()) - throw std::bad_cast(); - return val.get(); - } - }; - - /** - * Base class that represents a token payload. - * Contains Convenience accessors for common claims. - */ - class payload { - protected: - std::unordered_map payload_claims; - public: - /** - * Check if issuer is present ("iss") - * \return true if present, false otherwise - */ - bool has_issuer() const noexcept { return has_payload_claim("iss"); } - /** - * Check if subject is present ("sub") - * \return true if present, false otherwise - */ - bool has_subject() const noexcept { return has_payload_claim("sub"); } - /** - * Check if audience is present ("aud") - * \return true if present, false otherwise - */ - bool has_audience() const noexcept { return has_payload_claim("aud"); } - /** - * Check if expires is present ("exp") - * \return true if present, false otherwise - */ - bool has_expires_at() const noexcept { return has_payload_claim("exp"); } - /** - * Check if not before is present ("nbf") - * \return true if present, false otherwise - */ - bool has_not_before() const noexcept { return has_payload_claim("nbf"); } - /** - * Check if issued at is present ("iat") - * \return true if present, false otherwise - */ - bool has_issued_at() const noexcept { return has_payload_claim("iat"); } - /** - * Check if token id is present ("jti") - * \return true if present, false otherwise - */ - bool has_id() const noexcept { return has_payload_claim("jti"); } - /** - * Get issuer claim - * \return issuer as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_issuer() const { return get_payload_claim("iss").as_string(); } - /** - * Get subject claim - * \return subject as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_subject() const { return get_payload_claim("sub").as_string(); } - /** - * Get audience claim - * \return audience as a set of strings - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a set (Should not happen in a valid token) - */ - std::set get_audience() const { - auto aud = get_payload_claim("aud"); - if(aud.get_type() == jwt::claim::type::string) return { aud.as_string()}; - else return aud.as_set(); - } - /** - * Get expires claim - * \return expires as a date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_expires_at() const { return get_payload_claim("exp").as_date(); } - /** - * Get not valid before claim - * \return nbf date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_not_before() const { return get_payload_claim("nbf").as_date(); } - /** - * Get issued at claim - * \return issued at as date in utc - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a date (Should not happen in a valid token) - */ - const date get_issued_at() const { return get_payload_claim("iat").as_date(); } - /** - * Get id claim - * \return id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_id() const { return get_payload_claim("jti").as_string(); } - /** - * Check if a payload claim is present - * \return true if claim was present, false otherwise - */ - bool has_payload_claim(const std::string& name) const noexcept { return payload_claims.count(name) != 0; } - /** - * Get payload claim - * \return Requested claim - * \throws std::runtime_error If claim was not present - */ - const claim& get_payload_claim(const std::string& name) const { - if (!has_payload_claim(name)) - throw std::runtime_error("claim not found"); - return payload_claims.at(name); - } - /** - * Get all payload claims - * \return map of claims - */ - std::unordered_map get_payload_claims() const { return payload_claims; } - }; - - /** - * Base class that represents a token header. - * Contains Convenience accessors for common claims. - */ - class header { - protected: - std::unordered_map header_claims; - public: - /** - * Check if algortihm is present ("alg") - * \return true if present, false otherwise - */ - bool has_algorithm() const noexcept { return has_header_claim("alg"); } - /** - * Check if type is present ("typ") - * \return true if present, false otherwise - */ - bool has_type() const noexcept { return has_header_claim("typ"); } - /** - * Check if content type is present ("cty") - * \return true if present, false otherwise - */ - bool has_content_type() const noexcept { return has_header_claim("cty"); } - /** - * Check if key id is present ("kid") - * \return true if present, false otherwise - */ - bool has_key_id() const noexcept { return has_header_claim("kid"); } - /** - * Get algorithm claim - * \return algorithm as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_algorithm() const { return get_header_claim("alg").as_string(); } - /** - * Get type claim - * \return type as a string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_type() const { return get_header_claim("typ").as_string(); } - /** - * Get content type claim - * \return content type as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_content_type() const { return get_header_claim("cty").as_string(); } - /** - * Get key id claim - * \return key id as string - * \throws std::runtime_error If claim was not present - * \throws std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - const std::string& get_key_id() const { return get_header_claim("kid").as_string(); } - /** - * Check if a header claim is present - * \return true if claim was present, false otherwise - */ - bool has_header_claim(const std::string& name) const noexcept { return header_claims.count(name) != 0; } - /** - * Get header claim - * \return Requested claim - * \throws std::runtime_error If claim was not present - */ - const claim& get_header_claim(const std::string& name) const { - if (!has_header_claim(name)) - throw std::runtime_error("claim not found"); - return header_claims.at(name); - } - /** - * Get all header claims - * \return map of claims - */ - std::unordered_map get_header_claims() const { return header_claims; } - }; - - /** - * Class containing all information about a decoded token - */ - class decoded_jwt : public header, public payload { - protected: - /// Unmodifed token, as passed to constructor - const std::string token; - /// Header part decoded from base64 - std::string header; - /// Unmodified header part in base64 - std::string header_base64; - /// Payload part decoded from base64 - std::string payload; - /// Unmodified payload part in base64 - std::string payload_base64; - /// Signature part decoded from base64 - std::string signature; - /// Unmodified signature part in base64 - std::string signature_base64; - public: - /** - * Constructor - * Parses a given token - * \param token The token to parse - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json - */ - explicit decoded_jwt(const std::string& token) - : token(token) - { - auto hdr_end = token.find('.'); - if (hdr_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); - auto payload_end = token.find('.', hdr_end + 1); - if (payload_end == std::string::npos) - throw std::invalid_argument("invalid token supplied"); - header = header_base64 = token.substr(0, hdr_end); - payload = payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); - signature = signature_base64 = token.substr(payload_end + 1); - - // Fix padding: JWT requires padding to get removed - auto fix_padding = [](std::string& str) { - switch (str.size() % 4) { - case 1: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - case 2: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - case 3: - str += alphabet::base64url::fill(); -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) - [[fallthrough]]; -#endif -#endif - default: - break; - } - }; - fix_padding(header); - fix_padding(payload); - fix_padding(signature); - - header = base::decode(header); - payload = base::decode(payload); - signature = base::decode(signature); - - auto parse_claims = [](const std::string& str) { - std::unordered_map res; - picojson::value val; - if (!picojson::parse(val, str).empty()) - throw std::runtime_error("Invalid json"); - - for (auto& e : val.get()) { res.insert({ e.first, claim(e.second) }); } - - return res; - }; - - header_claims = parse_claims(header); - payload_claims = parse_claims(payload); - } - - /** - * Get token string, as passed to constructor - * \return token as passed to constructor - */ - const std::string& get_token() const { return token; } - /** - * Get header part as json string - * \return header part after base64 decoding - */ - const std::string& get_header() const { return header; } - /** - * Get payload part as json string - * \return payload part after base64 decoding - */ - const std::string& get_payload() const { return payload; } - /** - * Get signature part as json string - * \return signature part after base64 decoding - */ - const std::string& get_signature() const { return signature; } - /** - * Get header part as base64 string - * \return header part before base64 decoding - */ - const std::string& get_header_base64() const { return header_base64; } - /** - * Get payload part as base64 string - * \return payload part before base64 decoding - */ - const std::string& get_payload_base64() const { return payload_base64; } - /** - * Get signature part as base64 string - * \return signature part before base64 decoding - */ - const std::string& get_signature_base64() const { return signature_base64; } - - }; - - /** - * Builder class to build and sign a new token - * Use jwt::create() to get an instance of this class. - */ - class builder { - std::unordered_map header_claims; - std::unordered_map payload_claims; - - builder() {} - friend builder create(); - public: - /** - * Set a header claim. - * \param id Name of the claim - * \param c Claim to add - * \return *this to allow for method chaining - */ - builder& set_header_claim(const std::string& id, claim c) { header_claims[id] = std::move(c); return *this; } - /** - * Set a payload claim. - * \param id Name of the claim - * \param c Claim to add - * \return *this to allow for method chaining - */ - builder& set_payload_claim(const std::string& id, claim c) { payload_claims[id] = std::move(c); return *this; } - /** - * Set algorithm claim - * You normally don't need to do this, as the algorithm is automatically set if you don't change it. - * \param str Name of algorithm - * \return *this to allow for method chaining - */ - builder& set_algorithm(const std::string& str) { return set_header_claim("alg", claim(str)); } - /** - * Set type claim - * \param str Type to set - * \return *this to allow for method chaining - */ - builder& set_type(const std::string& str) { return set_header_claim("typ", claim(str)); } - /** - * Set content type claim - * \param str Type to set - * \return *this to allow for method chaining - */ - builder& set_content_type(const std::string& str) { return set_header_claim("cty", claim(str)); } - /** - * Set key id claim - * \param str Key id to set - * \return *this to allow for method chaining - */ - builder& set_key_id(const std::string& str) { return set_header_claim("kid", claim(str)); } - /** - * Set issuer claim - * \param str Issuer to set - * \return *this to allow for method chaining - */ - builder& set_issuer(const std::string& str) { return set_payload_claim("iss", claim(str)); } - /** - * Set subject claim - * \param str Subject to set - * \return *this to allow for method chaining - */ - builder& set_subject(const std::string& str) { return set_payload_claim("sub", claim(str)); } - /** - * Set audience claim - * \param l Audience set - * \return *this to allow for method chaining - */ - builder& set_audience(const std::set& l) { return set_payload_claim("aud", claim(l)); } - /** - * Set audience claim - * \param aud Single audience - * \return *this to allow for method chaining - */ - builder& set_audience(const std::string& aud) { return set_payload_claim("aud", claim(aud)); } - /** - * Set expires at claim - * \param d Expires time - * \return *this to allow for method chaining - */ - builder& set_expires_at(const date& d) { return set_payload_claim("exp", claim(d)); } - /** - * Set not before claim - * \param d First valid time - * \return *this to allow for method chaining - */ - builder& set_not_before(const date& d) { return set_payload_claim("nbf", claim(d)); } - /** - * Set issued at claim - * \param d Issued at time, should be current time - * \return *this to allow for method chaining - */ - builder& set_issued_at(const date& d) { return set_payload_claim("iat", claim(d)); } - /** - * Set id claim - * \param str ID to set - * \return *this to allow for method chaining - */ - builder& set_id(const std::string& str) { return set_payload_claim("jti", claim(str)); } - - /** - * Sign token and return result - * \param algo Instance of an algorithm to sign the token with - * \return Final token as a string - */ - template - std::string sign(const T& algo) { - this->set_algorithm(algo.name()); - - picojson::object obj_header; - for (auto& e : header_claims) { - obj_header.insert({ e.first, e.second.to_json() }); - } - picojson::object obj_payload; - for (auto& e : payload_claims) { - obj_payload.insert({ e.first, e.second.to_json() }); - } - - auto encode = [](const std::string& data) { - auto base = base::encode(data); - auto pos = base.find(alphabet::base64url::fill()); - base = base.substr(0, pos); - return base; - }; - - std::string header = encode(picojson::value(obj_header).serialize()); - std::string payload = encode(picojson::value(obj_payload).serialize()); - - std::string token = header + "." + payload; - - return token + "." + encode(algo.sign(token)); - } - }; - - /** - * Verifier class used to check if a decoded token contains all claims required by your application and has a valid signature. - */ - template - class verifier { - struct algo_base { - virtual ~algo_base() {} - virtual void verify(const std::string& data, const std::string& sig) = 0; - }; - template - struct algo : public algo_base { - T alg; - explicit algo(T a) : alg(a) {} - virtual void verify(const std::string& data, const std::string& sig) override { - alg.verify(data, sig); - } - }; - - /// Required claims - std::unordered_map claims; - /// Leeway time for exp, nbf and iat - size_t default_leeway = 0; - /// Instance of clock type - Clock clock; - /// Supported algorithms - std::unordered_map> algs; - public: - /** - * Constructor for building a new verifier instance - * \param c Clock instance - */ - explicit verifier(Clock c) : clock(c) {} - - /** - * Set default leeway to use. - * \param leeway Default leeway to use if not specified otherwise - * \return *this to allow chaining - */ - verifier& leeway(size_t leeway) { default_leeway = leeway; return *this; } - /** - * Set leeway for expires at. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for expires at. - * \return *this to allow chaining - */ - verifier& expires_at_leeway(size_t leeway) { return with_claim("exp", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set leeway for not before. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for not before. - * \return *this to allow chaining - */ - verifier& not_before_leeway(size_t leeway) { return with_claim("nbf", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set leeway for issued at. - * If not specified the default leeway will be used. - * \param leeway Set leeway to use for issued at. - * \return *this to allow chaining - */ - verifier& issued_at_leeway(size_t leeway) { return with_claim("iat", claim(std::chrono::system_clock::from_time_t(leeway))); } - /** - * Set an issuer to check for. - * Check is casesensitive. - * \param iss Issuer to check for. - * \return *this to allow chaining - */ - verifier& with_issuer(const std::string& iss) { return with_claim("iss", claim(iss)); } - /** - * Set a subject to check for. - * Check is casesensitive. - * \param sub Subject to check for. - * \return *this to allow chaining - */ - verifier& with_subject(const std::string& sub) { return with_claim("sub", claim(sub)); } - /** - * Set an audience to check for. - * If any of the specified audiences is not present in the token the check fails. - * \param aud Audience to check for. - * \return *this to allow chaining - */ - verifier& with_audience(const std::set& aud) { return with_claim("aud", claim(aud)); } - /** - * Set an id to check for. - * Check is casesensitive. - * \param id ID to check for. - * \return *this to allow chaining - */ - verifier& with_id(const std::string& id) { return with_claim("jti", claim(id)); } - /** - * Specify a claim to check for. - * \param name Name of the claim to check for - * \param c Claim to check for - * \return *this to allow chaining - */ - verifier& with_claim(const std::string& name, claim c) { claims[name] = c; return *this; } - - /** - * Add an algorithm available for checking. - * \param alg Algorithm to allow - * \return *this to allow chaining - */ - template - verifier& allow_algorithm(Algorithm alg) { - algs[alg.name()] = std::make_shared>(alg); - return *this; - } - - /** - * Verify the given token. - * \param jwt Token to check - * \throws token_verification_exception Verification failed - */ - void verify(const decoded_jwt& jwt) const { - const std::string data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); - const std::string sig = jwt.get_signature(); - const std::string& algo = jwt.get_algorithm(); - if (algs.count(algo) == 0) - throw token_verification_exception("wrong algorithm"); - algs.at(algo)->verify(data, sig); - - auto assert_claim_eq = [](const decoded_jwt& jwt, const std::string& key, const claim& c) { - if (!jwt.has_payload_claim(key)) - throw token_verification_exception("decoded_jwt is missing " + key + " claim"); - auto& jc = jwt.get_payload_claim(key); - if (jc.get_type() != c.get_type()) - throw token_verification_exception("claim " + key + " type mismatch"); - if (c.get_type() == claim::type::int64) { - if (c.as_date() != jc.as_date()) - throw token_verification_exception("claim " + key + " does not match expected"); - } - else if (c.get_type() == claim::type::array) { - auto s1 = c.as_set(); - auto s2 = jc.as_set(); - if (s1.size() != s2.size()) - throw token_verification_exception("claim " + key + " does not match expected"); - auto it1 = s1.cbegin(); - auto it2 = s2.cbegin(); - while (it1 != s1.cend() && it2 != s2.cend()) { - if (*it1++ != *it2++) - throw token_verification_exception("claim " + key + " does not match expected"); - } - } - else if (c.get_type() == claim::type::string) { - if (c.as_string() != jc.as_string()) - throw token_verification_exception("claim " + key + " does not match expected"); - } - else throw token_verification_exception("internal error"); - }; - - auto time = clock.now(); - - if (jwt.has_expires_at()) { - auto leeway = claims.count("exp") == 1 ? std::chrono::system_clock::to_time_t(claims.at("exp").as_date()) : default_leeway; - auto exp = jwt.get_expires_at(); - if (time > exp + std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - if (jwt.has_issued_at()) { - auto leeway = claims.count("iat") == 1 ? std::chrono::system_clock::to_time_t(claims.at("iat").as_date()) : default_leeway; - auto iat = jwt.get_issued_at(); - if (time < iat - std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - if (jwt.has_not_before()) { - auto leeway = claims.count("nbf") == 1 ? std::chrono::system_clock::to_time_t(claims.at("nbf").as_date()) : default_leeway; - auto nbf = jwt.get_not_before(); - if (time < nbf - std::chrono::seconds(leeway)) - throw token_verification_exception("token expired"); - } - for (auto& c : claims) - { - if (c.first == "exp" || c.first == "iat" || c.first == "nbf") { - // Nothing to do here, already checked - } - else if (c.first == "aud") { - if (!jwt.has_audience()) - throw token_verification_exception("token doesn't contain the required audience"); - auto aud = jwt.get_audience(); - auto expected = c.second.as_set(); - for (auto& e : expected) - if (aud.count(e) == 0) - throw token_verification_exception("token doesn't contain the required audience"); - } - else { - assert_claim_eq(jwt, c.first, c.second); - } - } - } - }; - - /** - * Create a verifier using the given clock - * \param c Clock instance to use - * \return verifier instance - */ - template - verifier verify(Clock c) { - return verifier(c); - } - - /** - * Default clock class using std::chrono::system_clock as a backend. - */ - struct default_clock { - std::chrono::system_clock::time_point now() const { - return std::chrono::system_clock::now(); - } - }; - - /** - * Create a verifier using the default clock - * \return verifier instance - */ - inline - verifier verify() { - return verify({}); - } - - /** - * Return a builder instance to create a new token - */ - inline - builder create() { - return builder(); - } - - /** - * Decode a token - * \param token Token to decode - * \return Decoded token - * \throws std::invalid_argument Token is not in correct format - * \throws std::runtime_error Base64 decoding failed or invalid json - */ - inline - decoded_jwt decode(const std::string& token) { - return decoded_jwt(token); - } -} diff --git a/src/jwt-cpp/jwt-cpp.sln b/src/jwt-cpp/jwt-cpp.sln deleted file mode 100644 index ef5abc2a6..000000000 --- a/src/jwt-cpp/jwt-cpp.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jwt-cpp", "jwt-cpp.vcxproj", "{1CA8C676-7F8E-434C-9069-8F20A562E6E9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.ActiveCfg = Debug|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x64.Build.0 = Debug|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.ActiveCfg = Debug|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Debug|x86.Build.0 = Debug|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.ActiveCfg = Release|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x64.Build.0 = Release|x64 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.ActiveCfg = Release|Win32 - {1CA8C676-7F8E-434C-9069-8F20A562E6E9}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/jwt-cpp/jwt-cpp.vcxproj b/src/jwt-cpp/jwt-cpp.vcxproj deleted file mode 100644 index 7d791a4d4..000000000 --- a/src/jwt-cpp/jwt-cpp.vcxproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {1CA8C676-7F8E-434C-9069-8F20A562E6E9} - Win32Proj - jwtcpp - 8.1 - - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - - - Level3 - Disabled - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - gtest.lib;gtest_main.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/jwt-cpp/jwt-cpp.vcxproj.filters b/src/jwt-cpp/jwt-cpp.vcxproj.filters deleted file mode 100644 index 6419b2d22..000000000 --- a/src/jwt-cpp/jwt-cpp.vcxproj.filters +++ /dev/null @@ -1,36 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Headerdateien - - - Headerdateien - - - Headerdateien - - - - - Quelldateien - - - Quelldateien - - - \ No newline at end of file diff --git a/src/jwt-cpp/vcpkg/CONTROL b/src/jwt-cpp/vcpkg/CONTROL deleted file mode 100644 index d29834fc6..000000000 --- a/src/jwt-cpp/vcpkg/CONTROL +++ /dev/null @@ -1,3 +0,0 @@ -Source: jwt-cpp -Version: 2019-04-20 -Description: A header only library for creating and validating json web tokens in c++ \ No newline at end of file diff --git a/src/jwt-cpp/vcpkg/fix-picojson.patch b/src/jwt-cpp/vcpkg/fix-picojson.patch deleted file mode 100644 index 44c04fe58..000000000 --- a/src/jwt-cpp/vcpkg/fix-picojson.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h -index ec56810..a26fd97 100644 ---- a/include/jwt-cpp/jwt.h -+++ b/include/jwt-cpp/jwt.h -@@ -1,6 +1,6 @@ - #pragma once - #define PICOJSON_USE_INT64 --#include "picojson.h" -+#include "picojson/picojson.h" - #include "base.h" - #include - #include diff --git a/src/jwt-cpp/vcpkg/fix-warning.patch b/src/jwt-cpp/vcpkg/fix-warning.patch deleted file mode 100644 index d013a7782..000000000 --- a/src/jwt-cpp/vcpkg/fix-warning.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/include/jwt-cpp/base.h b/include/jwt-cpp/base.h -index dfca7fc..4d05c0b 100644 ---- a/include/jwt-cpp/base.h -+++ b/include/jwt-cpp/base.h -@@ -2,6 +2,10 @@ - #include - #include - -+#ifdef _MSC_VER -+#pragma warning(disable : 4267) -+#endif -+ - namespace jwt { - namespace alphabet { - struct base64 { -diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h -index ec56810..313cef2 100644 ---- a/include/jwt-cpp/jwt.h -+++ b/include/jwt-cpp/jwt.h -@@ -12,6 +12,11 @@ - #include - #include - -+#ifdef _MSC_VER -+#pragma warning(disable : 4267) -+#pragma warning(disable : 4067) -+#endif -+ - //If openssl version less than 1.1 - #if OPENSSL_VERSION_NUMBER < 269484032 - #define OPENSSL10 diff --git a/src/jwt-cpp/vcpkg/portfile.cmake b/src/jwt-cpp/vcpkg/portfile.cmake deleted file mode 100644 index 1e10e3c21..000000000 --- a/src/jwt-cpp/vcpkg/portfile.cmake +++ /dev/null @@ -1,23 +0,0 @@ -#header-only library -include(vcpkg_common_functions) - -set(SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src/jwt-cpp) - -vcpkg_from_github(OUT_SOURCE_PATH SOURCE_PATH - REPO Thalhammer/jwt-cpp - REF f0e37a79f605312686065405dd720fc197cc3df0 - SHA512 ae83c205dbb340dedc58d0d3f0e2453c4edcf5ce43b401f49d02692dc8a2a4b7260f1ced05ddfa7c1d5d6f92446e232629ddbdf67a58a119b50c5c8163591598 - HEAD_REF master - PATCHES fix-picojson.patch - fix-warning.patch) - -# Copy the constexpr header files -file(GLOB HEADER_FILES ${SOURCE_PATH}/include/jwt-cpp/*) -file(COPY ${HEADER_FILES} - DESTINATION ${CURRENT_PACKAGES_DIR}/include/jwt-cpp - REGEX "\.(gitattributes|gitignore|picojson.h)$" EXCLUDE) - -# Put the licence file where vcpkg expects it -file(COPY ${SOURCE_PATH}/LICENSE - DESTINATION ${CURRENT_PACKAGES_DIR}/share/jwt-cpp) -file(RENAME ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/LICENSE ${CURRENT_PACKAGES_DIR}/share/jwt-cpp/copyright) \ No newline at end of file diff --git a/src/libbcrypt/CMakeLists.txt b/src/libbcrypt/CMakeLists.txt deleted file mode 100644 index a0883eec5..000000000 --- a/src/libbcrypt/CMakeLists.txt +++ /dev/null @@ -1,90 +0,0 @@ -################################################################################### -# -# Copyright (c) 2014, webvariants GmbH, http://www.webvariants.de -# -# This file is released under the terms of the MIT license. You can find the -# complete text in the attached LICENSE file or online at: -# -# http://www.opensource.org/licenses/mit-license.php -# -# @author: Tino Rusch (tino.rusch@webvariants.de) -# -################################################################################### - -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) - -project(bcrypt) - -enable_language(ASM) - -set(MYLIB_VERSION_MAJOR 1) -set(MYLIB_VERSION_MINOR 0) -set(MYLIB_VERSION_PATCH 0) -set(MYLIB_VERSION_STRING ${MYLIB_VERSION_MAJOR}.${MYLIB_VERSION_MINOR}.${MYLIB_VERSION_PATCH}) - -# just doing cmake . will build a shared or static lib and honor existing environment setting -# to force build static, cmake . -DBUILD_SHARED_LIBS=Off -# to force build shared, cmake . -DBUILD_SHARED_LIBS=On - -if (NOT BUILD_SHARED_LIBS) - message ("Building a static library") -else () - message ("Building a shared library") -endif () - - -set( CMAKE_COLOR_MAKEFILE ON ) - -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall --std=c++11 -O3" ) -set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3" ) - -set( CMAKE_ASM_FLAGS "${CXXFLAGS} -x assembler-with-cpp") - -set( SRCFILES - ${CMAKE_CURRENT_SOURCE_DIR}/src/bcrypt.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_blowfish.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/crypt_gensalt.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/wrapper.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/x86.S -) - -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include/bcrypt) -include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include) - -add_library( - ${PROJECT_NAME} - ${SRCFILES} -) - -set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${MYLIB_VERSION_STRING} SOVERSION ${MYLIB_VERSION_MAJOR}) - -set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER include/bcrypt/BCrypt.hpp) - -target_include_directories(${PROJECT_NAME} PRIVATE include) -target_include_directories(${PROJECT_NAME} PRIVATE src) - -add_executable( ${PROJECT_NAME}_test ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) - -target_link_libraries( ${PROJECT_NAME}_test ${PROJECT_NAME}) - -include(GNUInstallDirs) - -install(TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/bcrypt) - -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - FILES_MATCHING PATTERN "*.h") - -SET(CPACK_GENERATOR "DEB") -SET(CPACK_SET_DESTDIR ON) - -SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Manuel Romei") -SET(CPACK_PACKAGE_VERSION "1.0.0") -SET(CPACK_PACKAGE_VERSION_MAJOR "1") -SET(CPACK_PACKAGE_VERSION_MINOR "0") -SET(CPACK_PACKAGE_VERSION_PATCH "0") - -INCLUDE(CPack) diff --git a/src/zm.h b/src/zm.h index 60fa67711..46d6edf08 100644 --- a/src/zm.h +++ b/src/zm.h @@ -21,18 +21,6 @@ #ifndef ZM_H #define ZM_H -#include "zm_config.h" -#include "zm_signal.h" -#ifdef SOLARIS -#undef DEFAULT_TYPE // pthread defines this which breaks StreamType DEFAULT_TYPE -#include // define strerror() and friends -#endif -#include "zm_logger.h" - -#include - -#include - -extern const char* self; +extern const char *self; #endif // ZM_H diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp new file mode 100644 index 000000000..74d677316 --- /dev/null +++ b/src/zm_analysis_thread.cpp @@ -0,0 +1,61 @@ +#include "zm_analysis_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_utils.h" + +AnalysisThread::AnalysisThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { + thread_ = std::thread(&AnalysisThread::Run, this); +} + +AnalysisThread::~AnalysisThread() { + Stop(); + if (thread_.joinable()) thread_.join(); +} + +void AnalysisThread::Start() { + if (thread_.joinable()) thread_.join(); + terminate_ = false; + Debug(3, "Starting analysis thread"); + thread_ = std::thread(&AnalysisThread::Run, this); +} + +void AnalysisThread::Run() { + Microseconds analysis_rate = Microseconds(monitor_->GetAnalysisRate()); + Seconds analysis_update_delay = Seconds(monitor_->GetAnalysisUpdateDelay()); + Debug(2, "AnalysisThread::Run() has an update delay of %" PRId64 "s", + static_cast(analysis_update_delay.count())); + + monitor_->UpdateAdaptiveSkip(); + + TimePoint last_analysis_update_time = std::chrono::steady_clock::now(); + TimePoint cur_time; + + while (!(terminate_ or zm_terminate)) { + // Some periodic updates are required for variable capturing framerate + if (analysis_update_delay != Seconds::zero()) { + cur_time = std::chrono::steady_clock::now(); + Debug(2, "Updating adaptive skip"); + if ((cur_time - last_analysis_update_time) > analysis_update_delay) { + analysis_rate = Microseconds(monitor_->GetAnalysisRate()); + monitor_->UpdateAdaptiveSkip(); + last_analysis_update_time = cur_time; + } + } + + Debug(2, "Analyzing"); + if (!monitor_->Analyse()) { + if (!(terminate_ or zm_terminate)) { + Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE); + Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); + std::this_thread::sleep_for(sleep_for); + } + } else if (analysis_rate != Microseconds::zero()) { + Debug(2, "Sleeping for %" PRId64 " us", int64(analysis_rate.count())); + std::this_thread::sleep_for(analysis_rate); + } else { + Debug(2, "Not sleeping"); + } + } +} diff --git a/src/zm_analysis_thread.h b/src/zm_analysis_thread.h new file mode 100644 index 000000000..f949aa729 --- /dev/null +++ b/src/zm_analysis_thread.h @@ -0,0 +1,29 @@ +#ifndef ZM_ANALYSIS_THREAD_H +#define ZM_ANALYSIS_THREAD_H + +#include +#include +#include + +class Monitor; + +class AnalysisThread { + public: + explicit AnalysisThread(Monitor *monitor); + ~AnalysisThread(); + AnalysisThread(AnalysisThread &rhs) = delete; + AnalysisThread(AnalysisThread &&rhs) = delete; + + void Start(); + void Stop() { terminate_ = true; } + bool Stopped() const { return terminate_; } + + private: + void Run(); + + Monitor *monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/src/zm_bigfont.h b/src/zm_bigfont.h deleted file mode 100644 index 96ba479dd..000000000 --- a/src/zm_bigfont.h +++ /dev/null @@ -1,6155 +0,0 @@ -/***********************************************************/ -/* */ -/* Font file generated by schrorg */ -/* based on the font file generated by rthelen */ -/* using utils/mk_bigfont.pl */ -/* */ -/***********************************************************/ - -static unsigned int bigfontdata[] = { - - /* 0 0x00 '^A' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 1 0x01 '^B' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 2 0x02 '^C' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 3 0x03 '^D' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 4 0x04 '^E' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 5 0x05 '^F' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 6 0x06 '^G' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 7 0x07 '^H' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 8 0x08 '^I' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 9 0x09 '^J' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 10 0x0a '^K' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 11 0x0b '^L' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 12 0x0c '^M' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 13 0x0d '^N' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 14 0x0e '^O' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 15 0x0f '^P' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 16 0x10 '^Q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 17 0x11 '^R' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 18 0x12 '^S' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 19 0x13 '^T' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 20 0x14 '^U' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 21 0x15 '^V' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 22 0x16 '^W' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 23 0x17 '^X' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 24 0x18 '^Y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 25 0x19 '^Z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 26 0x1a '^[' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 27 0x1b '^\' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 28 0x1c '^]' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 29 0x1d '^^' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 30 0x1e '^_' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 31 0x1f '^`' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 32 0x20 ' ' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 33 0x21 '!' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 34 0x22 '"' */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 35 0x23 '#' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 36 0x24 '$' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 37 0x25 '%' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 38 0x26 '&' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 39 0x27 ''' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 40 0x28 '(' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 41 0x29 ')' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 42 0x2a '*' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 43 0x2b '+' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 44 0x2c ',' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 45 0x2d '-' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 46 0x2e '.' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 47 0x2f '/' */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 48 0x30 '0' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 49 0x31 '1' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 50 0x32 '2' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 51 0x33 '3' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 52 0x34 '4' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 53 0x35 '5' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 54 0x36 '6' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 55 0x37 '7' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 56 0x38 '8' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 57 0x39 '9' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 58 0x3a ':' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 59 0x3b ';' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 60 0x3c '<' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 61 0x3d '=' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 62 0x3e '>' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 63 0x3f '?' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 64 0x40 '@' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 65 0x41 'A' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 66 0x42 'B' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 67 0x43 'C' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 68 0x44 'D' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 69 0x45 'E' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 70 0x46 'F' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 71 0x47 'G' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 72 0x48 'H' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 73 0x49 'I' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 74 0x4a 'J' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 75 0x4b 'K' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 76 0x4c 'L' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 77 0x4d 'M' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 78 0x4e 'N' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 79 0x4f 'O' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 80 0x50 'P' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 81 0x51 'Q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 82 0x52 'R' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 83 0x53 'S' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 84 0x54 'T' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 85 0x55 'U' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 86 0x56 'V' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 87 0x57 'W' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 88 0x58 'X' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 89 0x59 'Y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 90 0x5a 'Z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 91 0x5b '[' */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 92 0x5c '\' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0xc, /* 000000000000 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 93 0x5d ']' */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 94 0x5e '^' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 95 0x5f '_' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 96 0x60 '`' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 97 0x61 'a' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 98 0x62 'b' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 99 0x63 'c' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 100 0x64 'd' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 101 0x65 'e' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 102 0x66 'f' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 103 0x67 'g' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 104 0x68 'h' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 105 0x69 'i' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 106 0x6a 'j' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 107 0x6b 'k' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 108 0x6c 'l' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 109 0x6d 'm' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 110 0x6e 'n' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 111 0x6f 'o' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 112 0x70 'p' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 113 0x71 'q' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 114 0x72 'r' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 115 0x73 's' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 116 0x74 't' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 117 0x75 'u' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 118 0x76 'v' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 119 0x77 'w' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 120 0x78 'x' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 121 0x79 'y' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 122 0x7a 'z' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 123 0x7b '{' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 124 0x7c '|' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 125 0x7d '}' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 126 0x7e '~' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 127 0x7f '^?' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 128 0x80 '\200' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 129 0x81 '\201' */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 130 0x82 '\202' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 131 0x83 '\203' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 132 0x84 '\204' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 133 0x85 '\205' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 134 0x86 '\206' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 135 0x87 '\207' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 136 0x88 '\210' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 137 0x89 '\211' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 138 0x8a '\212' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 139 0x8b '\213' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 140 0x8c '\214' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 141 0x8d '\215' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 142 0x8e '\216' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 143 0x8f '\217' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 144 0x90 '\220' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 145 0x91 '\221' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 146 0x92 '\222' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 147 0x93 '\223' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 148 0x94 '\224' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 149 0x95 '\225' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 150 0x96 '\226' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 151 0x97 '\227' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 152 0x98 '\230' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 153 0x99 '\231' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 154 0x9a '\232' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 155 0x9b '\233' */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 156 0x9c '\234' */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 157 0x9d '\235' */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 158 0x9e '\236' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 159 0x9f '\237' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 160 0xa0 '\240' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 161 0xa1 '\241' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 162 0xa2 '\242' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 163 0xa3 '\243' */ - 0xf00, /* 0000 00000000 */ - 0xf00, /* 0000 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 164 0xa4 '\244' */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 165 0xa5 '\245' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 166 0xa6 '\246' */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 167 0xa7 '\247' */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 168 0xa8 '\250' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 169 0xa9 '\251' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xf30, /* 0000 00 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3f00, /* 00 00000000 */ - 0x3f00, /* 00 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 170 0xaa '\252' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3fcc, /* 00 00 00 */ - 0x3fcc, /* 00 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0xcfc, /* 0000 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 171 0xab '\253' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 172 0xac '\254' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 173 0xad '\255' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 174 0xae '\256' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3fc0, /* 00 000000 */ - 0x3fc0, /* 00 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 175 0xaf '\257' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 176 0xb0 '\260' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 177 0xb1 '\261' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 178 0xb2 '\262' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 179 0xb3 '\263' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 180 0xb4 '\264' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 181 0xb5 '\265' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x3f30, /* 00 00 0000 */ - 0x3f30, /* 00 00 0000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 182 0xb6 '\266' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 183 0xb7 '\267' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 184 0xb8 '\270' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 185 0xb9 '\271' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 186 0xba '\272' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 187 0xbb '\273' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x3f0, /* 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 188 0xbc '\274' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 189 0xbd '\275' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3cf0, /* 00 00 0000 */ - 0x3cf0, /* 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 190 0xbe '\276' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 191 0xbf '\277' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x30f0, /* 00 0000 0000 */ - 0x30f0, /* 00 0000 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0x3c30, /* 00 0000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 192 0xc0 '\300' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 193 0xc1 '\301' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 194 0xc2 '\302' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 195 0xc3 '\303' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xf0, /* 00000000 0000 */ - 0xf0, /* 00000000 0000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0xc00, /* 0000 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 196 0xc4 '\304' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c00, /* 00 0000000000 */ - 0x3c00, /* 00 0000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 197 0xc5 '\305' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3000, /* 00 000000000000 */ - 0x3000, /* 00 000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 198 0xc6 '\306' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 199 0xc7 '\307' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 200 0xc8 '\310' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0xc30, /* 0000 0000 0000 */ - 0x30c0, /* 00 0000 000000 */ - 0x30c0, /* 00 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 201 0xc9 '\311' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 202 0xca '\312' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 203 0xcb '\313' */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 204 0xcc '\314' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 205 0xcd '\315' */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 206 0xce '\316' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x33c0, /* 00 00 000000 */ - 0x33c0, /* 00 00 000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 207 0xcf '\317' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3330, /* 00 00 00 0000 */ - 0x3330, /* 00 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x33f0, /* 00 00 0000 */ - 0x3300, /* 00 00 00000000 */ - 0x3300, /* 00 00 00000000 */ - 0xcf0, /* 0000 00 0000 */ - 0xcf0, /* 0000 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 208 0xd0 '\320' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 209 0xd1 '\321' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 210 0xd2 '\322' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 211 0xd3 '\323' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0x330, /* 000000 00 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 212 0xd4 '\324' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 213 0xd5 '\325' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3c0, /* 000000 000000 */ - 0x3c0, /* 000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0xc0, /* 00000000 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 214 0xd6 '\326' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ff0, /* 00 0000 */ - 0x3ff0, /* 00 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 215 0xd7 '\327' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x300, /* 000000 00000000 */ - 0x300, /* 000000 00000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 216 0xd8 '\330' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xcc0, /* 0000 00 000000 */ - 0xcc0, /* 0000 00 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0x3030, /* 00 000000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x30, /* 0000000000 0000 */ - 0x30, /* 0000000000 0000 */ - 0xfc0, /* 0000 000000 */ - 0xfc0, /* 0000 000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 217 0xd9 '\331' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x3ffc, /* 00 00 */ - 0x3ffc, /* 00 00 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 218 0xda '\332' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 219 0xdb '\333' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 220 0xdc '\334' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 221 0xdd '\335' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 222 0xde '\336' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 223 0xdf '\337' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 224 0xe0 '\340' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 225 0xe1 '\341' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 226 0xe2 '\342' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 227 0xe3 '\343' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 228 0xe4 '\344' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 229 0xe5 '\345' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 230 0xe6 '\346' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 231 0xe7 '\347' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 232 0xe8 '\350' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 233 0xe9 '\351' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 234 0xea '\352' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 235 0xeb '\353' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 236 0xec '\354' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 237 0xed '\355' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 238 0xee '\356' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 239 0xef '\357' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 240 0xf0 '\360' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 241 0xf1 '\361' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 242 0xf2 '\362' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 243 0xf3 '\363' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 244 0xf4 '\364' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 245 0xf5 '\365' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 246 0xf6 '\366' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 247 0xf7 '\367' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 248 0xf8 '\370' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 249 0xf9 '\371' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 250 0xfa '\372' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 251 0xfb '\373' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 252 0xfc '\374' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 253 0xfd '\375' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 254 0xfe '\376' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - - /* 255 0xff '\377' */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0xff0, /* 0000 0000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - 0x0, /* 0000000000000000 */ - -}; diff --git a/src/zm_box.cpp b/src/zm_box.cpp index 4a69fbf6d..6129b7a29 100644 --- a/src/zm_box.cpp +++ b/src/zm_box.cpp @@ -15,9 +15,8 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// -#include "zm.h" #include "zm_box.h" // This section deliberately left blank diff --git a/src/zm_box.h b/src/zm_box.h index efc12cb18..20e30bcd1 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -20,14 +20,8 @@ #ifndef ZM_BOX_H #define ZM_BOX_H -#include "zm.h" #include "zm_coord.h" - -#ifndef SOLARIS -#include -#else #include -#endif // // Class used for storing a box, which is defined as a region @@ -39,26 +33,30 @@ private: Coord size; public: - inline Box() { } - explicit inline Box( int p_size ) : lo( 0, 0 ), hi ( p_size-1, p_size-1 ), size( Coord::Range( hi, lo ) ) { } + inline Box() : lo(0,0), hi(0,0), size(0,0) { } + explicit inline Box(unsigned int p_size) : lo(0, 0), hi(p_size-1, p_size-1), size(Coord::Range(hi, lo)) { } inline Box( int p_x_size, int p_y_size ) : lo( 0, 0 ), hi ( p_x_size-1, p_y_size-1 ), size( Coord::Range( hi, lo ) ) { } inline Box( int lo_x, int lo_y, int hi_x, int hi_y ) : lo( lo_x, lo_y ), hi( hi_x, hi_y ), size( Coord::Range( hi, lo ) ) { } inline Box( const Coord &p_lo, const Coord &p_hi ) : lo( p_lo ), hi( p_hi ), size( Coord::Range( hi, lo ) ) { } inline const Coord &Lo() const { return lo; } inline int LoX() const { return lo.X(); } + inline int LoX(int p_lo_x) { return lo.X(p_lo_x); } inline int LoY() const { return lo.Y(); } + inline int LoY(int p_lo_y) { return lo.Y(p_lo_y); } inline const Coord &Hi() const { return hi; } inline int HiX() const { return hi.X(); } + inline int HiX(int p_hi_x) { return hi.X(p_hi_x); } inline int HiY() const { return hi.Y(); } + inline int HiY(int p_hi_y) { return hi.Y(p_hi_y); } inline const Coord &Size() const { return size; } inline int Width() const { return size.X(); } inline int Height() const { return size.Y(); } inline int Area() const { return size.X()*size.Y(); } inline const Coord Centre() const { - int mid_x = int(round(lo.X()+(size.X()/2.0))); - int mid_y = int(round(lo.Y()+(size.Y()/2.0))); + int mid_x = int(std::round(lo.X()+(size.X()/2.0))); + int mid_y = int(std::round(lo.Y()+(size.Y()/2.0))); return Coord( mid_x, mid_y ); } inline bool Inside( const Coord &coord ) const diff --git a/src/zm_buffer.cpp b/src/zm_buffer.cpp index 73c9603e1..b4b970eb3 100644 --- a/src/zm_buffer.cpp +++ b/src/zm_buffer.cpp @@ -17,65 +17,74 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include - -#include "zm.h" #include "zm_buffer.h" -unsigned int Buffer::assign( const unsigned char *pStorage, unsigned int pSize ) -{ - if ( mAllocation < pSize ) - { +#include + +unsigned int Buffer::assign(const unsigned char *pStorage, unsigned int pSize) { + if ( mAllocation < pSize ) { delete[] mStorage; mAllocation = pSize; mHead = mStorage = new unsigned char[pSize]; } mSize = pSize; - memcpy( mStorage, pStorage, mSize ); + memcpy(mStorage, pStorage, mSize); mHead = mStorage; mTail = mHead + mSize; - return( mSize ); + return mSize; } -unsigned int Buffer::expand( unsigned int count ) -{ +unsigned int Buffer::expand(unsigned int count) { int spare = mAllocation - mSize; int headSpace = mHead - mStorage; int tailSpace = spare - headSpace; int width = mTail - mHead; - if ( spare > (int)count ) - { - if ( tailSpace < (int)count ) - { - memmove( mStorage, mHead, mSize ); + if ( spare >= static_cast(count) ) { + // There is enough space in the allocation might need to shift everything over though + // + if ( tailSpace < static_cast(count) ) { + // if there is extra space at the head, shift everything over + memmove(mStorage, mHead, mSize); mHead = mStorage; mTail = mHead + width; } - } - else - { + } else { mAllocation += count; unsigned char *newStorage = new unsigned char[mAllocation]; - if ( mStorage ) - { - memcpy( newStorage, mHead, mSize ); + if ( mStorage ) { + memcpy(newStorage, mHead, mSize); delete[] mStorage; } mStorage = newStorage; mHead = mStorage; mTail = mHead + width; } - return( mSize ); + return mSize; } -int Buffer::read_into( int sd, unsigned int bytes ) { +int Buffer::read_into(int sd, unsigned int bytes) { // Make sure there is enough space this->expand(bytes); - int bytes_read = read( sd, mTail, bytes ); - if ( bytes_read > 0 ) { + Debug(3, "Reading %u btes", bytes); + int bytes_read = ::read(sd, mTail, bytes); + if (bytes_read > 0) { mTail += bytes_read; mSize += bytes_read; } return bytes_read; } + +int Buffer::read_into(int sd, unsigned int bytes, struct timeval timeout) { + fd_set set; + FD_ZERO(&set); /* clear the set */ + FD_SET(sd, &set); /* add our file descriptor to the set */ + int rv = select(sd + 1, &set, NULL, NULL, &timeout); + if (rv == -1) { + Error("Error %d %s from select", errno, strerror(errno)); + return rv; + } else if (rv == 0) { + Debug(1, "timeout"); /* a timeout occured */ + return 0; + } + return read_into(sd, bytes); +} diff --git a/src/zm_buffer.h b/src/zm_buffer.h index a4b373b6b..c2b509e13 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -20,159 +20,174 @@ #ifndef ZM_BUFFER_H #define ZM_BUFFER_H -#include "zm.h" +#include "zm_logger.h" +#include -#include - -class Buffer -{ -protected: +class Buffer { + protected: unsigned char *mStorage; unsigned int mAllocation; unsigned int mSize; unsigned char *mHead; unsigned char *mTail; -public: - Buffer() : mStorage( nullptr ), mAllocation( 0 ), mSize( 0 ), mHead( nullptr ), mTail( nullptr ) { + public: + Buffer() : + mStorage(nullptr), + mAllocation(0), + mSize(0), + mHead(nullptr), + mTail(nullptr) { } - explicit Buffer( unsigned int pSize ) : mAllocation( pSize ), mSize( 0 ) { + explicit Buffer(unsigned int pSize) : mAllocation(pSize), mSize(0) { mHead = mStorage = new unsigned char[mAllocation]; + if (mAllocation) *mHead = '\0'; mTail = mHead; } - Buffer( const unsigned char *pStorage, unsigned int pSize ) : mAllocation( pSize ), mSize( pSize ) { + Buffer(const unsigned char *pStorage, unsigned int pSize) : + mAllocation(pSize), mSize(pSize) { mHead = mStorage = new unsigned char[mSize]; - memcpy( mStorage, pStorage, mSize ); + std::memcpy(mStorage, pStorage, mSize); mTail = mHead + mSize; } - Buffer( const Buffer &buffer ) : mAllocation( buffer.mSize ), mSize( buffer.mSize ) { + Buffer(const Buffer &buffer) : + mAllocation(buffer.mSize), + mSize(buffer.mSize) { mHead = mStorage = new unsigned char[mSize]; - memcpy( mStorage, buffer.mHead, mSize ); + std::memcpy(mStorage, buffer.mHead, mSize); mTail = mHead + mSize; } ~Buffer() { delete[] mStorage; } - unsigned char *head() const { return( mHead ); } - unsigned char *tail() const { return( mTail ); } - unsigned int size() const { return( mSize ); } + unsigned char *head() const { return mHead; } + unsigned char *tail() const { return mTail; } + unsigned int size() const { return mSize; } bool empty() const { return( mSize == 0 ); } unsigned int size( unsigned int pSize ) { if ( mSize < pSize ) { - expand( pSize-mSize ); + expand(pSize-mSize); } - return( mSize ); + return mSize; } - //unsigned int Allocation() const { return( mAllocation ); } + // unsigned int Allocation() const { return( mAllocation ); } void clear() { mSize = 0; mHead = mTail = mStorage; } - unsigned int assign( const unsigned char *pStorage, unsigned int pSize ); - unsigned int assign( const Buffer &buffer ) { - return( assign( buffer.mHead, buffer.mSize ) ); + unsigned int assign(const unsigned char *pStorage, unsigned int pSize); + unsigned int assign(const Buffer &buffer) { + return assign(buffer.mHead, buffer.mSize); } // Trim from the front of the buffer - unsigned int consume( unsigned int count ) { - if ( count > mSize ) { - Warning( "Attempt to consume %d bytes of buffer, size is only %d bytes", count, mSize ); + unsigned int consume(unsigned int count) { + if (count > mSize) { + Warning("Attempt to consume %d bytes of buffer, size is only %d bytes", + count, mSize); count = mSize; } mHead += count; mSize -= count; - tidy( 0 ); - return( count ); + tidy(0); + return count; } // Trim from the end of the buffer - unsigned int shrink( unsigned int count ) { - if ( count > mSize ) { - Warning( "Attempt to shrink buffer by %d bytes, size is only %d bytes", count, mSize ); + unsigned int shrink(unsigned int count) { + if (count > mSize) { + Warning("Attempt to shrink buffer by %d bytes, size is only %d bytes", + count, mSize); count = mSize; } mSize -= count; - if ( mTail > (mHead + mSize) ) + if (mTail > (mHead + mSize)) mTail = mHead + mSize; - tidy( 0 ); - return( count ); + tidy(0); + return count; } // Add to the end of the buffer - unsigned int expand( unsigned int count ); + unsigned int expand(unsigned int count); // Return pointer to the first pSize bytes and advance the head - unsigned char *extract( unsigned int pSize ) { - if ( pSize > mSize ) { - Warning( "Attempt to extract %d bytes of buffer, size is only %d bytes", pSize, mSize ); + // We don't call tidy here because it is assumed the ram will be used afterwards + // This differs from consume + unsigned char *extract(unsigned int pSize) { + if (pSize > mSize) { + Warning("Attempt to extract %d bytes of buffer, size is only %d bytes", + pSize, mSize); pSize = mSize; } unsigned char *oldHead = mHead; mHead += pSize; mSize -= pSize; - tidy( 0 ); - return( oldHead ); + return oldHead; } // Add bytes to the end of the buffer - unsigned int append( const unsigned char *pStorage, unsigned int pSize ) { - expand( pSize ); - memcpy( mTail, pStorage, pSize ); + unsigned int append(const unsigned char *pStorage, unsigned int pSize) { + expand(pSize); + std::memcpy(mTail, pStorage, pSize); mTail += pSize; mSize += pSize; - return( mSize ); + return mSize; } - unsigned int append( const char *pStorage, unsigned int pSize ) { - return( append( (const unsigned char *)pStorage, pSize ) ); + unsigned int append(const char *pStorage, unsigned int pSize) { + return append((const unsigned char *)pStorage, pSize); } - unsigned int append( const Buffer &buffer ) { - return( append( buffer.mHead, buffer.mSize ) ); + unsigned int append(const Buffer &buffer) { + return append(buffer.mHead, buffer.mSize); } - void tidy( bool level=0 ) { - if ( mHead != mStorage ) { - if ( mSize == 0 ) + void tidy(bool level=0) { + if (mHead != mStorage) { + if (mSize == 0) { mHead = mTail = mStorage; - else if ( level ) { - if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize ) { - memcpy( mStorage, mHead, mSize ); +//*mHead = '\0'; + } else if (level) { + if (((uintptr_t)mHead-(uintptr_t)mStorage) > mSize) { + std::memcpy(mStorage, mHead, mSize); mHead = mStorage; mTail = mHead + mSize; } } + } else if (mSize == 0) { + *mHead = '\0'; } } - Buffer &operator=( const Buffer &buffer ) { - assign( buffer ); - return( *this ); + Buffer &operator=(const Buffer &buffer) { + assign(buffer); + return *this; } - Buffer &operator+=( const Buffer &buffer ) { - append( buffer ); - return( *this ); + Buffer &operator+=(const Buffer &buffer) { + append(buffer); + return *this; } - Buffer &operator+=( unsigned int count ) { - expand( count ); - return( *this ); + Buffer &operator+=(unsigned int count) { + expand(count); + return *this; } - Buffer &operator-=( unsigned int count ) { - consume( count ); - return( *this ); + Buffer &operator-=(unsigned int count) { + consume(count); + return *this; } operator unsigned char *() const { - return( mHead ); + return mHead; } operator char *() const { - return( (char *)mHead ); + return reinterpret_cast(mHead); } unsigned char *operator+(int offset) const { - return( (unsigned char *)(mHead+offset) ); + return (unsigned char *)(mHead+offset); } unsigned char operator[](int index) const { - return( *(mHead+index) ); + return *(mHead+index); } operator int () const { - return( (int)mSize ); + return static_cast(mSize); } - int read_into( int sd, unsigned int bytes ); + int read_into(int sd, unsigned int bytes); + int read_into(int sd, unsigned int bytes, struct timeval timeout); }; #endif // ZM_BUFFER_H diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index 0b4b208a8..c56cc34c7 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -17,11 +17,12 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_camera.h" +#include "zm_monitor.h" + Camera::Camera( - unsigned int p_monitor_id, + const Monitor *monitor, SourceType p_type, unsigned int p_width, unsigned int p_height, @@ -34,18 +35,30 @@ Camera::Camera( bool p_capture, bool p_record_audio ) : - monitor_id(p_monitor_id), + monitor(monitor), type(p_type), width(p_width), height(p_height), colours(p_colours), - subpixelorder(p_subpixelorder), + subpixelorder(p_subpixelorder), brightness(p_brightness), hue(p_hue), colour(p_colour), contrast(p_contrast), capture(p_capture), record_audio(p_record_audio), + mVideoStreamId(-1), + mAudioStreamId(-1), + mVideoCodecContext(nullptr), + mAudioCodecContext(nullptr), + mVideoStream(nullptr), + mAudioStream(nullptr), + mFormatContext(nullptr), + mSecondFormatContext(nullptr), + mFirstVideoPTS(0), + mFirstAudioPTS(0), + mLastVideoPTS(0), + mLastAudioPTS(0), bytes(0) { linesize = width * colours; @@ -53,21 +66,48 @@ Camera::Camera( imagesize = height * linesize; Debug(2, "New camera id: %d width: %d line size: %d height: %d colours: %d subpixelorder: %d capture: %d", - monitor_id, width, linesize, height, colours, subpixelorder, capture); - - monitor = nullptr; + monitor->Id(), width, linesize, height, colours, subpixelorder, capture); } Camera::~Camera() { + if ( mFormatContext ) { + // Should also free streams + avformat_free_context(mFormatContext); + } + if ( mSecondFormatContext ) { + // Should also free streams + avformat_free_context(mSecondFormatContext); + } + mVideoStream = nullptr; + mAudioStream = nullptr; } -Monitor *Camera::getMonitor() { - if ( ! monitor ) - monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); - return monitor; -} - -void Camera::setMonitor(Monitor *p_monitor) { - monitor = p_monitor; - monitor_id = monitor->Id(); +AVStream *Camera::getVideoStream() { + if ( !mVideoStream ) { + if ( !mFormatContext ) + mFormatContext = avformat_alloc_context(); + Debug(1, "Allocating avstream"); + mVideoStream = avformat_new_stream(mFormatContext, nullptr); + if ( mVideoStream ) { + mVideoStream->time_base = (AVRational){1, 1000000}; // microseconds as base frame rate +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mVideoStream->codecpar->width = width; + mVideoStream->codecpar->height = height; + mVideoStream->codecpar->format = GetFFMPEGPixelFormat(colours, subpixelorder); + mVideoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + mVideoStream->codecpar->codec_id = AV_CODEC_ID_NONE; + Debug(1, "Allocating avstream %p %p %d", mVideoStream, mVideoStream->codecpar, mVideoStream->codecpar->codec_id); +#else + mVideoStream->codec->width = width; + mVideoStream->codec->height = height; + mVideoStream->codec->pix_fmt = GetFFMPEGPixelFormat(colours, subpixelorder); + mVideoStream->codec->codec_type = AVMEDIA_TYPE_VIDEO; + mVideoStream->codec->codec_id = AV_CODEC_ID_NONE; +#endif + } else { + Error("Can't create video stream"); + } + mVideoStreamId = mVideoStream->index; + } + return mVideoStream; } diff --git a/src/zm_camera.h b/src/zm_camera.h index 7f0ab0796..fb117dcfb 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -20,14 +20,12 @@ #ifndef ZM_CAMERA_H #define ZM_CAMERA_H -#include -#include - #include "zm_image.h" +#include +#include -class Camera; - -#include "zm_monitor.h" +class Monitor; +class ZMPacket; // // Abstract base class for cameras. This is intended just to express @@ -37,8 +35,7 @@ class Camera { protected: typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC, VNC_SRC } SourceType; - unsigned int monitor_id; - Monitor * monitor; // Null on instantiation, set as soon as possible. + const Monitor *monitor; SourceType type; unsigned int width; unsigned int linesize; @@ -53,15 +50,37 @@ protected: int contrast; bool capture; bool record_audio; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mVideoCodecContext; + AVCodecContext *mAudioCodecContext; + AVStream *mVideoStream; + AVStream *mAudioStream; + AVFormatContext *mFormatContext; // One for video, one for audio + AVFormatContext *mSecondFormatContext; // One for video, one for audio + int64_t mFirstVideoPTS; + int64_t mFirstAudioPTS; + int64_t mLastVideoPTS; + int64_t mLastAudioPTS; unsigned int bytes; public: - Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + Camera( + const Monitor* monitor, + SourceType p_type, + unsigned int p_width, + unsigned int p_height, + int p_colours, + int p_subpixelorder, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); virtual ~Camera(); - unsigned int getId() const { return monitor_id; } - Monitor *getMonitor(); - void setMonitor( Monitor *p_monitor ); SourceType Type() const { return type; } bool IsLocal() const { return type == LOCAL_SRC; } bool IsRemote() const { return type == REMOTE_SRC; } @@ -78,6 +97,20 @@ public: unsigned int Pixels() const { return pixels; } unsigned long long ImageSize() const { return imagesize; } unsigned int Bytes() const { return bytes; }; + int getFrequency() { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + return mAudioStream ? mAudioStream->codecpar->sample_rate : -1; +#else + return mAudioStream ? mAudioStream->codec->sample_rate : -1; +#endif + } + int getChannels() { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + return mAudioStream ? mAudioStream->codecpar->channels : -1; +#else + return mAudioStream ? mAudioStream->codec->channels : -1; +#endif + } virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; } virtual int Hue( int/*p_hue*/=-1 ) { return -1; } @@ -91,11 +124,17 @@ public: //return (type == FFMPEG_SRC )||(type == REMOTE_SRC); } + virtual AVStream *getVideoStream(); + virtual AVStream *getAudioStream() { return mAudioStream; }; + virtual AVCodecContext *getVideoCodecContext() { return mVideoCodecContext; }; + virtual AVCodecContext *getAudioCodecContext() { return mAudioCodecContext; }; + int getVideoStreamId() { return mVideoStreamId; }; + int getAudioStreamId() { return mAudioStreamId; }; + virtual int PrimeCapture() { return 0; } virtual int PreCapture() = 0; - virtual int Capture(Image &image) = 0; + virtual int Capture(ZMPacket &p) = 0; virtual int PostCapture() = 0; - virtual int CaptureAndRecord(Image &image, timeval recording, char* event_directory) = 0; virtual int Close() = 0; }; diff --git a/src/zm_comms.cpp b/src/zm_comms.cpp index b7231d4fd..7f933d16f 100644 --- a/src/zm_comms.cpp +++ b/src/zm_comms.cpp @@ -20,858 +20,722 @@ #include "zm_comms.h" #include "zm_logger.h" - -#include +#include // for debug output +#include +#include +#include // for snprintf #include -#include -//#include -#if defined(BSD) -#include -#else -#include -#endif - -//#include +#include #include #include -#include -#include // for debug output -#include // for snprintf +#include #ifdef SOLARIS #include // define FIONREAD #endif -int CommsBase::readV( int iovcnt, /* const void *, int, */ ... ) { +int ZM::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; - //struct iovec *iov = (struct iovec *)alloca( sizeof(struct iovec)*iovcnt ); + iovec iov[iovcnt]; - va_start( arg_ptr, iovcnt ); - for ( int i = 0; i < iovcnt; i++ ) - { - iov[i].iov_base = va_arg( arg_ptr, void * ); - iov[i].iov_len = va_arg( arg_ptr, int ); + va_start(arg_ptr, iovcnt); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = va_arg(arg_ptr, void *); + iov[i].iov_len = va_arg(arg_ptr, int); } - va_end( arg_ptr ); + va_end(arg_ptr); - int nBytes = ::readv( mRd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno) ); - return( nBytes ); + int nBytes = ::readv(mRd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); + } + return nBytes; } -int CommsBase::writeV( int iovcnt, /* const void *, int, */ ... ) { +int ZM::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; - //struct iovec *iov = (struct iovec *)alloca( sizeof(struct iovec)*iovcnt ); + iovec iov[iovcnt]; - va_start( arg_ptr, iovcnt ); - for ( int i = 0; i < iovcnt; i++ ) - { - iov[i].iov_base = va_arg( arg_ptr, void * ); - iov[i].iov_len = va_arg( arg_ptr, int ); + va_start(arg_ptr, iovcnt); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = va_arg(arg_ptr, void *); + iov[i].iov_len = va_arg(arg_ptr, int); } - va_end( arg_ptr ); + va_end(arg_ptr); - ssize_t nBytes = ::writev( mWd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno) ); - return( nBytes ); + ssize_t nBytes = ::writev(mWd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); + } + return nBytes; } -bool Pipe::open() -{ - if ( ::pipe( mFd ) < 0 ) - { - Error( "pipe(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool ZM::Pipe::open() { + if (::pipe(mFd) < 0) { + Error("pipe(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Pipe::close() -{ - if ( mFd[0] > -1 ) ::close( mFd[0] ); +bool ZM::Pipe::close() { + if (mFd[0] > -1) { + ::close(mFd[0]); + } mFd[0] = -1; - if ( mFd[1] > -1 ) ::close( mFd[1] ); + if (mFd[1] > -1) { + ::close(mFd[1]); + } mFd[1] = -1; - return( true ); + return true; } -bool Pipe::setBlocking( bool blocking ) -{ +bool ZM::Pipe::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ - if ( (flags = fcntl( mFd[1], F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mFd[1], F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - if ( blocking ) - { + if (blocking) { flags &= ~O_NONBLOCK; - } - else - { + } else { flags |= O_NONBLOCK; } - if ( fcntl( mFd[1], F_SETFL, flags ) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (fcntl(mFd[1], F_SETFL, flags) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -SockAddr::SockAddr( const struct sockaddr *addr ) : mAddr( addr ) -{ -} +ZM::SockAddr *ZM::SockAddr::newSockAddr(const sockaddr &addr, socklen_t len) { + if ((addr.sa_family == AF_INET) && (len == SockAddrInet::addrSize())) { + return new SockAddrInet((const sockaddr_in *) &addr); + } else if ((addr.sa_family == AF_UNIX) && (len == SockAddrUnix::addrSize())) { + return new SockAddrUnix((const sockaddr_un *) &addr); + } -SockAddr *SockAddr::newSockAddr( const struct sockaddr &addr, socklen_t len ) -{ - if ( addr.sa_family == AF_INET && len == SockAddrInet::addrSize() ) - { - return( new SockAddrInet( (const struct sockaddr_in *)&addr ) ); - } - else if ( addr.sa_family == AF_UNIX && len == SockAddrUnix::addrSize() ) - { - return( new SockAddrUnix( (const struct sockaddr_un *)&addr ) ); - } - Error( "Unable to create new SockAddr from addr family %d with size %d", addr.sa_family, len ); + Error("Unable to create new SockAddr from addr family %d with size %d", addr.sa_family, len); return nullptr; } -SockAddr *SockAddr::newSockAddr( const SockAddr *addr ) -{ - if ( !addr ) +ZM::SockAddr *ZM::SockAddr::newSockAddr(const SockAddr *addr) { + if (!addr) { return nullptr; + } - if ( addr->getDomain() == AF_INET ) - { - return( new SockAddrInet( *(SockAddrInet *)addr ) ); + if (addr->getDomain() == AF_INET) { + return new SockAddrInet(*(SockAddrInet *) addr); + } else if (addr->getDomain() == AF_UNIX) { + return new SockAddrUnix(*(SockAddrUnix *) addr); } - else if ( addr->getDomain() == AF_UNIX ) - { - return( new SockAddrUnix( *(SockAddrUnix *)addr ) ); - } - Error( "Unable to create new SockAddr from addr family %d", addr->getDomain() ); + + Error("Unable to create new SockAddr from addr family %d", addr->getDomain()); return nullptr; } -SockAddrInet::SockAddrInet() : SockAddr( (struct sockaddr *)&mAddrIn ) -{ -} +bool ZM::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); -bool SockAddrInet::resolve( const char *host, const char *serv, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); - - struct hostent *hostent=nullptr; - if ( !(hostent = ::gethostbyname( host ) ) ) - { - Error( "gethostbyname( %s ), h_errno = %d", host, h_errno ); - return( false ); + hostent *hostent = nullptr; + if (!(hostent = ::gethostbyname(host))) { + Error("gethostbyname(%s), h_errno = %d", host, h_errno); + return false; } - struct servent *servent=nullptr; - if ( !(servent = ::getservbyname( serv, proto ) ) ) - { - Error( "getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno) ); - return( false ); + servent *servent = nullptr; + if (!(servent = ::getservbyname(serv, proto))) { + Error("getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno)); + return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; - mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; + mAddrIn.sin_addr.s_addr = ((in_addr *) (hostent->h_addr))->s_addr; - return( true ); + return true; } -bool SockAddrInet::resolve( const char *host, int port, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool ZM::SockAddrInet::resolve(const char *host, int port, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct hostent *hostent=nullptr; - if ( !(hostent = ::gethostbyname( host ) ) ) - { - Error( "gethostbyname( %s ), h_errno = %d", host, h_errno ); - return( false ); + hostent *hostent = nullptr; + if (!(hostent = ::gethostbyname(host))) { + Error("gethostbyname(%s), h_errno = %d", host, h_errno); + return false; } mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; - mAddrIn.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr))->s_addr; - return( true ); + mAddrIn.sin_addr.s_addr = ((in_addr *) (hostent->h_addr))->s_addr; + return true; } -bool SockAddrInet::resolve( const char *serv, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct servent *servent=nullptr; - if ( !(servent = ::getservbyname( serv, proto ) ) ) - { - Error( "getservbyname( %s ), errno = %d, error = %s", serv, errno, strerror(errno) ); - return( false ); + servent *servent = nullptr; + if (!(servent = ::getservbyname(serv, proto))) { + Error("getservbyname(%s), errno = %d, error = %s", serv, errno, strerror(errno)); + return false; } mAddrIn.sin_port = servent->s_port; mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; - return( true ); + return true; } -bool SockAddrInet::resolve( int port, const char *proto ) -{ - memset( &mAddrIn, 0, sizeof(mAddrIn) ); +bool ZM::SockAddrInet::resolve(int port, const char *proto) { + memset(&mAddrIn, 0, sizeof(mAddrIn)); mAddrIn.sin_port = htons(port); mAddrIn.sin_family = AF_INET; mAddrIn.sin_addr.s_addr = INADDR_ANY; - return( true ); + return true; } -SockAddrUnix::SockAddrUnix() : SockAddr( (struct sockaddr *)&mAddrUn ) -{ -} +bool ZM::SockAddrUnix::resolve(const char *path, const char *proto) { + memset(&mAddrUn, 0, sizeof(mAddrUn)); -bool SockAddrUnix::resolve( const char *path, const char *proto ) -{ - memset( &mAddrUn, 0, sizeof(mAddrUn) ); - - strncpy( mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path) ); + strncpy(mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path)); mAddrUn.sun_family = AF_UNIX; - return( true ); + return true; } -bool Socket::socket() -{ - if ( mSd >= 0 ) - return( true ); +bool ZM::Socket::socket() { + if (mSd >= 0) { + return true; + } - if ( (mSd = ::socket( getDomain(), getType(), 0 ) ) < 0 ) - { - Error( "socket(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((mSd = ::socket(getDomain(), getType(), 0)) < 0) { + Error("socket(), errno = %d, error = %s", errno, strerror(errno)); + return false; } int val = 1; - (void)::setsockopt( mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val) ); - (void)::setsockopt( mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val) ); + ::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + ::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); mState = DISCONNECTED; - - return( true ); + return true; } -bool Socket::connect() -{ - if ( !socket() ) - return( false ); +bool ZM::Socket::connect() { + if (!socket()) { + return false; + } - if ( ::connect( mSd, mRemoteAddr->getAddr(), getAddrSize() ) == -1 ) - { - Error( "connect(), errno = %d, error = %s", errno, strerror(errno) ); + if (::connect(mSd, mRemoteAddr->getAddr(), getAddrSize()) == -1) { + Error("connect(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } mState = CONNECTED; - - return( true ); + return true; } -bool Socket::bind() -{ - if ( !socket() ) - return( false ); - - if ( ::bind( mSd, mLocalAddr->getAddr(), getAddrSize() ) == -1 ) - { - Error( "bind(), errno = %d, error = %s", errno, strerror(errno) ); - close(); - return( false ); +bool ZM::Socket::bind() { + if (!socket()) { + return false; } - return( true ); + + if (::bind(mSd, mLocalAddr->getAddr(), getAddrSize()) == -1) { + Error("bind(), errno = %d, error = %s", errno, strerror(errno)); + close(); + return false; + } + return true; } -bool Socket::listen() -{ - if ( ::listen( mSd, SOMAXCONN ) == -1 ) - { - Error( "listen(), errno = %d, error = %s", errno, strerror(errno) ); +bool ZM::Socket::listen() { + if (::listen(mSd, SOMAXCONN) == -1) { + Error("listen(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } mState = LISTENING; - - return( true ); + return true; } -bool Socket::accept() -{ - struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); +bool ZM::Socket::accept() { + sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); int newSd = -1; - if ( (newSd = ::accept( mSd, rem_addr, &rem_addr_size )) == -1 ) - { - Error( "accept(), errno = %d, error = %s", errno, strerror(errno) ); + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { + Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } - ::close( mSd ); + ::close(mSd); mSd = newSd; mState = CONNECTED; - - return( true ); + return true; } -bool Socket::accept( int &newSd ) -{ - struct sockaddr *rem_addr = mLocalAddr->getTempAddr(); +bool ZM::Socket::accept(int &newSd) { + sockaddr rem_addr = {}; socklen_t rem_addr_size = getAddrSize(); newSd = -1; - if ( (newSd = ::accept( mSd, rem_addr, &rem_addr_size )) == -1 ) - { - Error( "accept(), errno = %d, error = %s", errno, strerror(errno) ); + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { + Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); - return( false ); + return false; } - return( true ); + return true; } -bool Socket::close() -{ - if ( mSd > -1 ) ::close( mSd ); +bool ZM::Socket::close() { + if (mSd > -1) { + ::close(mSd); + } + mSd = -1; mState = CLOSED; - return( true ); + return true; } -int Socket::bytesToRead() const -{ +int ZM::Socket::bytesToRead() const { int bytes_to_read = 0; - if ( ioctl( mSd, FIONREAD, &bytes_to_read ) < 0 ) - { - Error( "ioctl(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (ioctl(mSd, FIONREAD, &bytes_to_read) < 0) { + Error("ioctl(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( bytes_to_read ); + return bytes_to_read; } -bool Socket::getBlocking( bool &blocking ) -{ +bool ZM::Socket::getBlocking(bool &blocking) { int flags; - if ( (flags = fcntl( mSd, F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mSd, F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - blocking = (flags & O_NONBLOCK); - return( true ); + blocking = flags & O_NONBLOCK; + return true; } -bool Socket::setBlocking( bool blocking ) -{ -#if 0 - // ioctl is apparently not recommended - int ioctl_arg = !blocking; - if ( ioctl( mSd, FIONBIO, &ioctl_arg ) < 0 ) - { - Error( "ioctl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); - } - return( true ); -#endif - +bool ZM::Socket::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ - if ( (flags = fcntl( mSd, F_GETFL )) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if ((flags = fcntl(mSd, F_GETFL)) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - if ( blocking ) - { + if (blocking) { flags &= ~O_NONBLOCK; - } - else - { + } else { flags |= O_NONBLOCK; } - if ( fcntl( mSd, F_SETFL, flags ) < 0 ) - { - Error( "fcntl(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (fcntl(mSd, F_SETFL, flags) < 0) { + Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getSendBufferSize( int &buffersize ) const -{ +int ZM::Socket::getSendBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); - if ( getsockopt( mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (getsockopt(mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( buffersize ); + return buffersize; } -bool Socket::getRecvBufferSize( int &buffersize ) const -{ +int ZM::Socket::getRecvBufferSize(int &buffersize) const { socklen_t optlen = sizeof(buffersize); - if ( getsockopt( mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( -1 ); + if (getsockopt(mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return -1; } - return( buffersize ); + return buffersize; } -bool Socket::setSendBufferSize( int buffersize ) -{ - if ( setsockopt( mSd, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool ZM::Socket::setSendBufferSize(int buffersize) { + if (setsockopt(mSd, SOL_SOCKET, SO_SNDBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::setRecvBufferSize( int buffersize ) -{ - if ( setsockopt( mSd, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); +bool ZM::Socket::setRecvBufferSize(int buffersize) { + if (setsockopt(mSd, SOL_SOCKET, SO_RCVBUF, (char *) &buffersize, sizeof(buffersize)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getRouting( bool &route ) const -{ +bool ZM::Socket::getRouting(bool &route) const { int dontRoute; socklen_t optlen = sizeof(dontRoute); - if ( getsockopt( mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (getsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } route = !dontRoute; - return( true ); + return true; } -bool Socket::setRouting( bool route ) -{ +bool ZM::Socket::setRouting(bool route) { int dontRoute = !route; - if ( setsockopt( mSd, SOL_SOCKET, SO_DONTROUTE, (char *)&dontRoute, sizeof(dontRoute)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (setsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, (char *) &dontRoute, sizeof(dontRoute)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool Socket::getNoDelay( bool &nodelay ) const -{ +bool ZM::Socket::getNoDelay(bool &nodelay) const { int int_nodelay; socklen_t optlen = sizeof(int_nodelay); - if ( getsockopt( mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen ) < 0 ) - { - Error( "getsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (getsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen) < 0) { + Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } nodelay = int_nodelay; - return( true ); + return true; } -bool Socket::setNoDelay( bool nodelay ) -{ +bool ZM::Socket::setNoDelay(bool nodelay) { int int_nodelay = nodelay; - if ( setsockopt( mSd, IPPROTO_TCP, TCP_NODELAY, (char *)&int_nodelay, sizeof(int_nodelay)) < 0 ) - { - Error( "setsockopt(), errno = %d, error = %s", errno, strerror(errno) ); - return( false ); + if (setsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, (char *) &int_nodelay, sizeof(int_nodelay)) < 0) { + Error("setsockopt(), errno = %d, error = %s", errno, strerror(errno)); + return false; } - return( true ); + return true; } -bool InetSocket::connect( const char *host, const char *serv ) -{ - struct addrinfo hints; - struct addrinfo *result, *rp; - int s; - char buf[255]; +bool ZM::InetSocket::connect(const char *host, const char *serv) { + addrinfo hints; + addrinfo *result, *rp; + int s; + char buf[255]; + mAddressFamily = AF_UNSPEC; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = getType(); + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + s = getaddrinfo(host, serv, &hints, &result); + if (s != 0) { + Error("connect(): getaddrinfo: %s", gai_strerror(s)); + return false; + } + + /* getaddrinfo() returns a list of address structures. + * Try each address until we successfully connect(2). + * If socket(2) (or connect(2)) fails, we (close the socket + * and) try the next address. */ + + for (rp = result; rp != nullptr; rp = rp->ai_next) { + if (mSd != -1) { + if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; /* Success */ + } + continue; + } + + memset(&buf, 0, sizeof(buf)); + if (rp->ai_family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *) rp->ai_addr)->sin_addr, buf, sizeof(buf) - 1); + } else if (rp->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, sizeof(buf) - 1); + } else { + strncpy(buf, "n/a", sizeof(buf) - 1); + } + + Debug(1, "connect(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); + mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (mSd == -1) { + continue; + } + + int val = 1; + + ::setsockopt(mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + ::setsockopt(mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + mAddressFamily = rp->ai_family; /* save AF_ for ctrl and data connections */ + + if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; /* Success */ + } + + ::close(mSd); + } + + freeaddrinfo(result); /* No longer needed */ + + if (rp == nullptr) { /* No address succeeded */ + Error("connect(), Could not connect"); mAddressFamily = AF_UNSPEC; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = getType(); - hints.ai_flags = 0; - hints.ai_protocol = 0; /* Any protocol */ + return false; + } - s = getaddrinfo(host, serv, &hints, &result); - if (s != 0) { - Error( "connect(): getaddrinfo: %s", gai_strerror(s) ); - return( false ); - } - - /* getaddrinfo() returns a list of address structures. - * Try each address until we successfully connect(2). - * If socket(2) (or connect(2)) fails, we (close the socket - * and) try the next address. */ - - for (rp = result; rp != nullptr; rp = rp->ai_next) { - if (mSd != -1) { - if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) - break; /* Success */ - continue; - } - memset(&buf, 0, sizeof(buf)); - if (rp->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); - } - else if (rp->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); - } - else { - strncpy(buf, "n/a", sizeof(buf)-1); - } - Debug( 1, "connect(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); - mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (mSd == -1) - continue; - - int val = 1; - - (void)::setsockopt( mSd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val) ); - (void)::setsockopt( mSd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val) ); - mAddressFamily = rp->ai_family; /* save AF_ for ctrl and data connections */ - - if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) - break; /* Success */ - - ::close(mSd); - } - - freeaddrinfo(result); /* No longer needed */ - - if (rp == nullptr) { /* No address succeeded */ - Error( "connect(), Could not connect" ); - mAddressFamily = AF_UNSPEC; - return( false ); - } - - mState = CONNECTED; - - return( true ); + mState = CONNECTED; + return true; } -bool InetSocket::connect( const char *host, int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool ZM::InetSocket::connect(const char *host, int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return connect( host, serv ); + return connect(host, serv); } -bool InetSocket::bind( const char * host, const char * serv ) -{ - struct addrinfo hints; - struct addrinfo *result, *rp; - int s; - char buf[255]; +bool ZM::InetSocket::bind(const char *host, const char *serv) { + addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ - hints.ai_socktype = getType(); - hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ - hints.ai_protocol = 0; /* Any protocol */ - hints.ai_canonname = nullptr; - hints.ai_addr = nullptr; - hints.ai_next = nullptr; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = getType(); + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = nullptr; + hints.ai_addr = nullptr; + hints.ai_next = nullptr; - s = getaddrinfo(host, serv, &hints, &result); - if (s != 0) { - Error( "bind(): getaddrinfo: %s", gai_strerror(s) ); - return( false ); + addrinfo *result, *rp; + int s = getaddrinfo(host, serv, &hints, &result); + if (s != 0) { + Error("bind(): getaddrinfo: %s", gai_strerror(s)); + return false; + } + + char buf[255]; + /* getaddrinfo() returns a list of address structures. + * Try each address until we successfully bind(2). + * If socket(2) (or bind(2)) fails, we (close the socket + * and) try the next address. */ + for (rp = result; rp != nullptr; rp = rp->ai_next) { + memset(&buf, 0, sizeof(buf)); + if (rp->ai_family == AF_INET) { + inet_ntop(AF_INET, &((sockaddr_in *) rp->ai_addr)->sin_addr, buf, sizeof(buf) - 1); + } else if (rp->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, sizeof(buf) - 1); + } else { + strncpy(buf, "n/a", sizeof(buf) - 1); } - /* getaddrinfo() returns a list of address structures. - * Try each address until we successfully bind(2). - * If socket(2) (or bind(2)) fails, we (close the socket - * and) try the next address. */ - for (rp = result; rp != nullptr; rp = rp->ai_next) { - memset(&buf, 0, sizeof(buf)); - if (rp->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)rp->ai_addr)->sin_addr, buf, sizeof(buf)-1); - } - else if (rp->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); - } - else { - strncpy(buf, "n/a", sizeof(buf)-1); - } - Debug( 1, "bind(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); - mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (mSd == -1) - continue; + Debug(1, "bind(): Trying '%s', family '%d', proto '%d'", buf, rp->ai_family, rp->ai_protocol); + mSd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (mSd == -1) { + continue; + } mState = DISCONNECTED; - if (::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0) - break; /* Success */ - - ::close(mSd); - mSd = -1; + if (::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0) { + break; /* Success */ } - if (rp == nullptr) { /* No address succeeded */ - Error( "bind(), Could not bind" ); - return( false ); - } + ::close(mSd); + mSd = -1; + } - freeaddrinfo(result); /* No longer needed */ + if (rp == nullptr) { /* No address succeeded */ + Error("bind(), Could not bind"); + return false; + } - return( true ); + freeaddrinfo(result); /* No longer needed */ + return true; } -bool InetSocket::bind( const char * serv ) -{ - return bind( nullptr, serv); +bool ZM::InetSocket::bind(const char *serv) { + return bind(nullptr, serv); } -bool InetSocket::bind( const char * host, int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool ZM::InetSocket::bind(const char *host, int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return bind( host, serv ); + return bind(host, serv); } -bool InetSocket::bind( int port ) -{ - char serv[8]; - snprintf(serv, sizeof(serv), "%d", port); +bool ZM::InetSocket::bind(int port) { + char serv[8]; + snprintf(serv, sizeof(serv), "%d", port); - return bind( nullptr, serv ); + return bind(nullptr, serv); } -bool TcpInetServer::listen() -{ - return( Socket::listen() ); +bool ZM::TcpInetServer::listen() { + return Socket::listen(); } -bool TcpInetServer::accept() -{ - return( Socket::accept() ); +bool ZM::TcpInetServer::accept() { + return Socket::accept(); } -bool TcpInetServer::accept( TcpInetSocket *&newSocket ) -{ +bool ZM::TcpInetServer::accept(TcpInetSocket *&newSocket) { int newSd = -1; newSocket = nullptr; - if ( !Socket::accept( newSd ) ) - return( false ); + if (!Socket::accept(newSd)) { + return false; + } - newSocket = new TcpInetSocket( *this, newSd ); - - return( true ); + newSocket = new TcpInetSocket(*this, newSd); + return true; } -bool TcpUnixServer::accept( TcpUnixSocket *&newSocket ) -{ +bool ZM::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { int newSd = -1; newSocket = nullptr; - if ( !Socket::accept( newSd ) ) - return( false ); + if (!Socket::accept(newSd)) { + return false; + } - newSocket = new TcpUnixSocket( *this, newSd ); - - return( true ); + newSocket = new TcpUnixSocket(*this, newSd); + return true; } -Select::Select() : mHasTimeout( false ), mMaxFd( -1 ) -{ -} - -Select::Select( struct timeval timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -Select::Select( int timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -Select::Select( double timeout ) : mMaxFd( -1 ) -{ - setTimeout( timeout ); -} - -void Select::setTimeout( int timeout ) -{ +void ZM::Select::setTimeout(int timeout) { mTimeout.tv_sec = timeout; mTimeout.tv_usec = 0; mHasTimeout = true; } -void Select::setTimeout( double timeout ) -{ +void ZM::Select::setTimeout(double timeout) { mTimeout.tv_sec = int(timeout); - mTimeout.tv_usec = suseconds_t((timeout-mTimeout.tv_sec)*1000000.0); + mTimeout.tv_usec = suseconds_t((timeout - mTimeout.tv_sec) * 1000000.0); mHasTimeout = true; } -void Select::setTimeout( struct timeval timeout ) -{ +void ZM::Select::setTimeout(timeval timeout) { mTimeout = timeout; mHasTimeout = true; } -void Select::clearTimeout() -{ +void ZM::Select::clearTimeout() { mHasTimeout = false; } -void Select::calcMaxFd() -{ +void ZM::Select::calcMaxFd() { mMaxFd = -1; - for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) - if ( (*iter)->getMaxDesc() > mMaxFd ) + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + if ((*iter)->getMaxDesc() > mMaxFd) mMaxFd = (*iter)->getMaxDesc(); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - if ( (*iter)->getMaxDesc() > mMaxFd ) + } + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + if ((*iter)->getMaxDesc() > mMaxFd) mMaxFd = (*iter)->getMaxDesc(); + } } -bool Select::addReader( CommsBase *comms ) -{ - if ( !comms->isOpen() ) - { - Error( "Unable to add closed reader" ); - return( false ); +bool ZM::Select::addReader(CommsBase *comms) { + if (!comms->isOpen()) { + Error("Unable to add closed reader"); + return false; } - std::pair result = mReaders.insert( comms ); - if ( result.second ) - if ( comms->getMaxDesc() > mMaxFd ) + std::pair result = mReaders.insert(comms); + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); - return( result.second ); + } + } + return result.second; } -bool Select::deleteReader( CommsBase *comms ) -{ - if ( !comms->isOpen() ) - { - Error( "Unable to add closed reader" ); - return( false ); +bool ZM::Select::deleteReader(CommsBase *comms) { + if (!comms->isOpen()) { + Error("Unable to add closed reader"); + return false; } - if ( mReaders.erase( comms ) ) - { + if (mReaders.erase(comms)) { calcMaxFd(); - return( true ); + return true; } - return( false ); + return false; } -void Select::clearReaders() -{ +void ZM::Select::clearReaders() { mReaders.clear(); mMaxFd = -1; } -bool Select::addWriter( CommsBase *comms ) -{ - std::pair result = mWriters.insert( comms ); - if ( result.second ) - if ( comms->getMaxDesc() > mMaxFd ) +bool ZM::Select::addWriter(CommsBase *comms) { + std::pair result = mWriters.insert(comms); + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); - return( result.second ); -} - -bool Select::deleteWriter( CommsBase *comms ) -{ - if ( mWriters.erase( comms ) ) - { - calcMaxFd(); - return( true ); + } } - return( false ); + return result.second; } -void Select::clearWriters() -{ +bool ZM::Select::deleteWriter(CommsBase *comms) { + if (mWriters.erase(comms)) { + calcMaxFd(); + return true; + } + return false; +} + +void ZM::Select::clearWriters() { mWriters.clear(); mMaxFd = -1; } -int Select::wait() -{ - struct timeval tempTimeout = mTimeout; - struct timeval *selectTimeout = mHasTimeout?&tempTimeout:nullptr; +int ZM::Select::wait() { + timeval tempTimeout = mTimeout; + timeval *selectTimeout = mHasTimeout ? &tempTimeout : nullptr; fd_set rfds; fd_set wfds; mReadable.clear(); FD_ZERO(&rfds); - for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) - FD_SET((*iter)->getReadDesc(),&rfds); + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + FD_SET((*iter)->getReadDesc(), &rfds); + } mWriteable.clear(); FD_ZERO(&wfds); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - FD_SET((*iter)->getWriteDesc(),&wfds); - - int nFound = select( mMaxFd+1, &rfds, &wfds, nullptr, selectTimeout ); - if( nFound == 0 ) - { - Debug( 1, "Select timed out" ); + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + FD_SET((*iter)->getWriteDesc(), &wfds); } - else if ( nFound < 0) - { - Error( "Select error: %s", strerror(errno) ); - } - else - { - for ( CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter ) - if ( FD_ISSET((*iter)->getReadDesc(),&rfds) ) - mReadable.push_back( *iter ); - for ( CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter ) - if ( FD_ISSET((*iter)->getWriteDesc(),&rfds) ) - mWriteable.push_back( *iter ); - } - return( nFound ); -} -const Select::CommsList &Select::getReadable() const -{ - return( mReadable ); -} - -const Select::CommsList &Select::getWriteable() const -{ - return( mWriteable ); + int nFound = select(mMaxFd + 1, &rfds, &wfds, nullptr, selectTimeout); + if (nFound == 0) { + Debug(1, "Select timed out"); + } else if (nFound < 0) { + Error("Select error: %s", strerror(errno)); + } else { + for (CommsSet::iterator iter = mReaders.begin(); iter != mReaders.end(); ++iter) { + if (FD_ISSET((*iter)->getReadDesc(), &rfds)) { + mReadable.push_back(*iter); + } + } + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + if (FD_ISSET((*iter)->getWriteDesc(), &rfds)) { + mWriteable.push_back(*iter); + } + } + } + return nFound; } diff --git a/src/zm_comms.h b/src/zm_comms.h index 46face33c..9126e5373 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -20,632 +20,580 @@ #ifndef ZM_COMMS_H #define ZM_COMMS_H -#include "zm_logger.h" #include "zm_exception.h" - -#include -#include +#include "zm_logger.h" +#include #include -#include -#include - #include -#include #include +#include +#include +#include #if defined(BSD) #include #include #endif +namespace ZM { + class CommsException : public Exception { -public: - explicit CommsException( const std::string &message ) : Exception( message ) { } + public: + explicit CommsException(const std::string &message) : Exception(message) {} }; class CommsBase { -protected: - const int &mRd; - const int &mWd; + protected: + CommsBase(const int &rd, const int &wd) : mRd(rd), mWd(wd) {} + virtual ~CommsBase() = default; -protected: - CommsBase( int &rd, int &wd ) : mRd( rd ), mWd( wd ) { - } - virtual ~CommsBase() { + public: + virtual bool close() = 0; + virtual bool isOpen() const = 0; + virtual bool isClosed() const = 0; + virtual bool setBlocking(bool blocking) = 0; + + public: + virtual int getReadDesc() const { return mRd; } + virtual int getWriteDesc() const { return mWd; } + int getMaxDesc() const { return mRd > mWd ? mRd : mWd; } + + virtual int read(void *msg, int len) { + ssize_t nBytes = ::read(mRd, msg, len); + if (nBytes < 0) { + Debug(1, "Read of %d bytes max on rd %d failed: %s", len, mRd, strerror(errno)); + } + return nBytes; } -public: - virtual bool close()=0; - virtual bool isOpen() const=0; - virtual bool isClosed() const=0; - virtual bool setBlocking( bool blocking )=0; - -public: - int getReadDesc() const { - return( mRd ); - } - int getWriteDesc() const { - return( mWd ); - } - int getMaxDesc() const { - return( mRd>mWd?mRd:mWd ); + virtual int write(const void *msg, int len) { + ssize_t nBytes = ::write(mWd, msg, len); + if (nBytes < 0) { + Debug(1, "Write of %d bytes on wd %d failed: %s", len, mWd, strerror(errno)); + } + return nBytes; } - virtual int read( void *msg, int len ) { - ssize_t nBytes = ::read( mRd, msg, len ); - if ( nBytes < 0 ) - Debug( 1, "Read of %d bytes max on rd %d failed: %s", len, mRd, strerror(errno) ); - return( nBytes ); + virtual int readV(const iovec *iov, int iovcnt) { + int nBytes = ::readv(mRd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); + } + return nBytes; } - virtual int write( const void *msg, int len ) { - ssize_t nBytes = ::write( mWd, msg, len ); - if ( nBytes < 0 ) - Debug( 1, "Write of %d bytes on wd %d failed: %s", len, mWd, strerror(errno) ); - return( nBytes ); + + virtual int writeV(const iovec *iov, int iovcnt) { + ssize_t nBytes = ::writev(mWd, iov, iovcnt); + if (nBytes < 0) { + Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); + } + return nBytes; } - virtual int readV( const struct iovec *iov, int iovcnt ) { - int nBytes = ::readv( mRd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno) ); - return( nBytes ); - } - virtual int writeV( const struct iovec *iov, int iovcnt ) { - ssize_t nBytes = ::writev( mWd, iov, iovcnt ); - if ( nBytes < 0 ) - Debug( 1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno) ); - return( nBytes ); - } - virtual int readV( int iovcnt, /* const void *msg1, int len1, */ ... ); - virtual int writeV( int iovcnt, /* const void *msg1, int len1, */ ... ); + + virtual int readV(int iovcnt, /* const void *msg1, int len1, */ ...); + virtual int writeV(int iovcnt, /* const void *msg1, int len1, */ ...); + + protected: + const int &mRd; + const int &mWd; }; class Pipe : public CommsBase { -protected: - int mFd[2]; - -public: - Pipe() : CommsBase( mFd[0], mFd[1] ) { + public: + Pipe() : CommsBase(mFd[0], mFd[1]) { mFd[0] = -1; mFd[1] = -1; } - ~Pipe() { - close(); - } -public: + ~Pipe() override { close(); } + bool open(); - bool close(); + bool close() override; - bool isOpen() const - { - return( mFd[0] != -1 && mFd[1] != -1 ); - } - int getReadDesc() const - { - return( mFd[0] ); - } - int getWriteDesc() const - { - return( mFd[1] ); - } + bool isOpen() const override { return mFd[0] != -1 && mFd[1] != -1; } + bool isClosed() const override { return !isOpen(); } + int getReadDesc() const override { return mFd[0]; } + int getWriteDesc() const override { return mFd[1]; } - bool setBlocking( bool blocking ); + bool setBlocking(bool blocking) override; + + protected: + int mFd[2]; }; class SockAddr { -private: - const struct sockaddr *mAddr; + public: + explicit SockAddr(const sockaddr *addr) : mAddr(addr) {} + virtual ~SockAddr() = default; -public: - explicit SockAddr( const struct sockaddr *addr ); - virtual ~SockAddr() { - } + static SockAddr *newSockAddr(const sockaddr &addr, socklen_t len); + static SockAddr *newSockAddr(const SockAddr *addr); - static SockAddr *newSockAddr( const struct sockaddr &addr, socklen_t len ); - static SockAddr *newSockAddr( const SockAddr *addr ); + int getDomain() const { return mAddr ? mAddr->sa_family : AF_UNSPEC; } + const sockaddr *getAddr() const { return mAddr; } - int getDomain() const { - return( mAddr?mAddr->sa_family:AF_UNSPEC ); - } + virtual socklen_t getAddrSize() const = 0; + virtual sockaddr *getTempAddr() const = 0; - const struct sockaddr *getAddr() const { - return( mAddr ); - } - virtual socklen_t getAddrSize() const=0; - virtual struct sockaddr *getTempAddr() const=0; + private: + const sockaddr *mAddr; }; class SockAddrInet : public SockAddr { -private: - struct sockaddr_in mAddrIn; - struct sockaddr_in mTempAddrIn; + public: + SockAddrInet() : SockAddr((sockaddr *) &mAddrIn) {} + explicit SockAddrInet(const SockAddrInet &addr) + : SockAddr((const sockaddr *) &mAddrIn), mAddrIn(addr.mAddrIn) {} + explicit SockAddrInet(const sockaddr_in *addr) + : SockAddr((const sockaddr *) &mAddrIn), mAddrIn(*addr) {} -public: - SockAddrInet(); - explicit SockAddrInet( const SockAddrInet &addr ) : SockAddr( (const struct sockaddr *)&mAddrIn ), mAddrIn( addr.mAddrIn ) { - } - explicit SockAddrInet( const struct sockaddr_in *addr ) : SockAddr( (const struct sockaddr *)&mAddrIn ), mAddrIn( *addr ) { - } + bool resolve(const char *host, const char *serv, const char *proto); + bool resolve(const char *host, int port, const char *proto); + bool resolve(const char *serv, const char *proto); + bool resolve(int port, const char *proto); + socklen_t getAddrSize() const override { return sizeof(mAddrIn); } + sockaddr *getTempAddr() const override { return (sockaddr *) &mTempAddrIn; } - bool resolve( const char *host, const char *serv, const char *proto ); - bool resolve( const char *host, int port, const char *proto ); - bool resolve( const char *serv, const char *proto ); - bool resolve( int port, const char *proto ); + static socklen_t addrSize() { return sizeof(sockaddr_in); } - socklen_t getAddrSize() const { - return( sizeof(mAddrIn) ); - } - struct sockaddr *getTempAddr() const { - return( (sockaddr *)&mTempAddrIn ); - } - -public: - static socklen_t addrSize() { - return( sizeof(sockaddr_in) ); - } + private: + sockaddr_in mAddrIn; + sockaddr_in mTempAddrIn; }; class SockAddrUnix : public SockAddr { -private: - struct sockaddr_un mAddrUn; - struct sockaddr_un mTempAddrUn; + public: + SockAddrUnix() : SockAddr((sockaddr *) &mAddrUn) {} + SockAddrUnix(const SockAddrUnix &addr) + : SockAddr((const sockaddr *) &mAddrUn), mAddrUn(addr.mAddrUn) {} + explicit SockAddrUnix(const sockaddr_un *addr) + : SockAddr((const sockaddr *) &mAddrUn), mAddrUn(*addr) {} -public: - SockAddrUnix(); - SockAddrUnix( const SockAddrUnix &addr ) : SockAddr( (const struct sockaddr *)&mAddrUn ), mAddrUn( addr.mAddrUn ) { - } - explicit SockAddrUnix( const struct sockaddr_un *addr ) : SockAddr( (const struct sockaddr *)&mAddrUn ), mAddrUn( *addr ) { - } + bool resolve(const char *path, const char *proto); - bool resolve( const char *path, const char *proto ); + socklen_t getAddrSize() const override { return sizeof(mAddrUn); } + sockaddr *getTempAddr() const override { return (sockaddr *) &mTempAddrUn; } - socklen_t getAddrSize() const { - return( sizeof(mAddrUn) ); - } - struct sockaddr *getTempAddr() const { - return( (sockaddr *)&mTempAddrUn ); - } + static socklen_t addrSize() { return sizeof(sockaddr_un); } -public: - static socklen_t addrSize() { - return( sizeof(sockaddr_un) ); - } + private: + sockaddr_un mAddrUn; + sockaddr_un mTempAddrUn; }; class Socket : public CommsBase { -protected: - typedef enum { CLOSED, DISCONNECTED, LISTENING, CONNECTED } State; + protected: + enum State { CLOSED, DISCONNECTED, LISTENING, CONNECTED }; -protected: - int mSd; - State mState; - SockAddr *mLocalAddr; - SockAddr *mRemoteAddr; + Socket() : CommsBase(mSd, mSd), + mSd(-1), + mState(CLOSED), + mLocalAddr(nullptr), + mRemoteAddr(nullptr) {} + Socket(const Socket &socket, int newSd) : CommsBase(mSd, mSd), + mSd(newSd), + mState(CONNECTED), + mLocalAddr(nullptr), + mRemoteAddr(nullptr) { + if (socket.mLocalAddr) + mLocalAddr = SockAddr::newSockAddr(mLocalAddr); + if (socket.mRemoteAddr) + mRemoteAddr = SockAddr::newSockAddr(mRemoteAddr); + } -protected: - Socket() : CommsBase( mSd, mSd ), mSd( -1 ), mState( CLOSED ), mLocalAddr( 0 ), mRemoteAddr( 0 ) { - } - Socket( const Socket &socket, int newSd ) : CommsBase( mSd, mSd ), mSd( newSd ), mState( CONNECTED ), mLocalAddr( 0 ), mRemoteAddr( 0 ) { - if ( socket.mLocalAddr ) - mLocalAddr = SockAddr::newSockAddr( mLocalAddr ); - if ( socket.mRemoteAddr ) - mRemoteAddr = SockAddr::newSockAddr( mRemoteAddr ); - } virtual ~Socket() { close(); delete mLocalAddr; delete mRemoteAddr; } -public: - bool isOpen() const { - return( !isClosed() ); - } - bool isClosed() const { - return( mState == CLOSED ); - } - bool isDisconnected() const { - return( mState == DISCONNECTED ); - } - bool isConnected() const { - return( mState == CONNECTED ); - } - virtual bool close(); + public: + bool isOpen() const override { return !isClosed(); } + bool isClosed() const override { return mState == CLOSED; } + bool isDisconnected() const { return mState == DISCONNECTED; } + bool isConnected() const { return mState == CONNECTED; } + bool close() override; -protected: - bool isListening() const { - return( mState == LISTENING ); + virtual int send(const void *msg, int len) const { + ssize_t nBytes = ::send(mSd, msg, len, 0); + if (nBytes < 0) { + Debug(1, "Send of %d bytes on sd %d failed: %s", len, mSd, strerror(errno)); + } + return nBytes; } -protected: - virtual bool socket(); - virtual bool bind(); + virtual int recv(void *msg, int len) const { + ssize_t nBytes = ::recv(mSd, msg, len, 0); + if (nBytes < 0) { + Debug(1, "Recv of %d bytes max on sd %d failed: %s", len, mSd, strerror(errno)); + } + return nBytes; + } -protected: - virtual bool connect(); - virtual bool listen(); - virtual bool accept(); - virtual bool accept( int & ); + virtual int send(const std::string &msg) const { + ssize_t nBytes = ::send(mSd, msg.data(), msg.size(), 0); + if (nBytes < 0) { + Debug(1, "Send of string '%s' (%zd bytes) on sd %d failed: %s", + msg.c_str(), + msg.size(), + mSd, + strerror(errno)); + } + return nBytes; + } -public: - virtual int send( const void *msg, int len ) const { - ssize_t nBytes = ::send( mSd, msg, len, 0 ); - if ( nBytes < 0 ) - Debug( 1, "Send of %d bytes on sd %d failed: %s", len, mSd, strerror(errno) ); - return( nBytes ); - } - virtual int recv( void *msg, int len ) const { - ssize_t nBytes = ::recv( mSd, msg, len, 0 ); - if ( nBytes < 0 ) - Debug( 1, "Recv of %d bytes max on sd %d failed: %s", len, mSd, strerror(errno) ); - return( nBytes ); - } - virtual int send( const std::string &msg ) const { - ssize_t nBytes = ::send( mSd, msg.data(), msg.size(), 0 ); - if ( nBytes < 0 ) - Debug( 1, "Send of string '%s' (%zd bytes) on sd %d failed: %s", msg.c_str(), msg.size(), mSd, strerror(errno) ); - return( nBytes ); - } - virtual int recv( std::string &msg ) const { + virtual int recv(std::string &msg) const { char buffer[msg.capacity()]; int nBytes = 0; - if ( (nBytes = ::recv( mSd, buffer, sizeof(buffer), 0 )) < 0 ) { - Debug( 1, "Recv of %zd bytes max to string on sd %d failed: %s", sizeof(buffer), mSd, strerror(errno) ); - return( nBytes ); + if ((nBytes = ::recv(mSd, buffer, sizeof(buffer), 0)) < 0) { + Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", sizeof(buffer), mSd, strerror(errno)); + return nBytes; } buffer[nBytes] = '\0'; msg = buffer; - return( nBytes ); + return nBytes; } - virtual int recv( std::string &msg, size_t maxLen ) const { + + virtual int recv(std::string &msg, size_t maxLen) const { char buffer[maxLen]; int nBytes = 0; - if ( (nBytes = ::recv( mSd, buffer, sizeof(buffer), 0 )) < 0 ) { - Debug( 1, "Recv of %zd bytes max to string on sd %d failed: %s", maxLen, mSd, strerror(errno) ); - return( nBytes ); + if ((nBytes = ::recv(mSd, buffer, sizeof(buffer), 0)) < 0) { + Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", maxLen, mSd, strerror(errno)); + return nBytes; } buffer[nBytes] = '\0'; msg = buffer; - return( nBytes ); + return nBytes; } + virtual int bytesToRead() const; - int getDesc() const { - return( mSd ); - } + int getDesc() const { return mSd; } //virtual bool isOpen() const //{ - //return( mSd != -1 ); + //return( mSd != -1 ); //} - virtual int getDomain() const=0; - virtual int getType() const=0; - virtual const char *getProtocol() const=0; + virtual int getDomain() const = 0; + virtual int getType() const = 0; + virtual const char *getProtocol() const = 0; - const SockAddr *getLocalAddr() const { - return( mLocalAddr ); - } - const SockAddr *getRemoteAddr() const { - return( mRemoteAddr ); - } - virtual socklen_t getAddrSize() const=0; + const SockAddr *getLocalAddr() const { return mLocalAddr; } + const SockAddr *getRemoteAddr() const { return mRemoteAddr; } + virtual socklen_t getAddrSize() const = 0; - bool getBlocking( bool &blocking ); - bool setBlocking( bool blocking ); + bool getBlocking(bool &blocking); + bool setBlocking(bool blocking) override; - bool getSendBufferSize( int & ) const; - bool getRecvBufferSize( int & ) const; + int getSendBufferSize(int &) const; + int getRecvBufferSize(int &) const; - bool setSendBufferSize( int ); - bool setRecvBufferSize( int ); + bool setSendBufferSize(int); + bool setRecvBufferSize(int); - bool getRouting( bool & ) const; - bool setRouting( bool ); + bool getRouting(bool &) const; + bool setRouting(bool); - bool getNoDelay( bool & ) const; - bool setNoDelay( bool ); + bool getNoDelay(bool &) const; + bool setNoDelay(bool); + + protected: + virtual bool isListening() const { return mState == LISTENING; } + + bool socket(); + bool bind(); + + bool connect(); + virtual bool listen(); + virtual bool accept(); + bool accept(int &); + + int mSd; + State mState; + SockAddr *mLocalAddr; + SockAddr *mRemoteAddr; }; -class InetSocket : virtual public Socket -{ -protected: - int mAddressFamily; +class InetSocket : virtual public Socket { + public: + int getDomain() const override { return mAddressFamily; } + socklen_t getAddrSize() const override { return SockAddrInet::addrSize(); } -public: -int getDomain() const { - return( mAddressFamily ); -} -virtual socklen_t getAddrSize() const { - return( SockAddrInet::addrSize() ); -} + protected: + bool connect(const char *host, const char *serv); + bool connect(const char *host, int port); -protected: - bool connect( const char *host, const char *serv ); - bool connect( const char *host, int port ); + bool bind(const char *host, const char *serv); + bool bind(const char *host, int port); + bool bind(const char *serv); + bool bind(int port); - bool bind( const char *host, const char *serv ); - bool bind( const char *host, int port ); - bool bind( const char *serv ); - bool bind( int port ); + int mAddressFamily; }; class UnixSocket : virtual public Socket { -public: - int getDomain() const { - return( AF_UNIX ); - } - virtual socklen_t getAddrSize() const { - return( SockAddrUnix::addrSize() ); - } + public: + int getDomain() const override { return AF_UNIX; } + socklen_t getAddrSize() const override { return SockAddrUnix::addrSize(); } -protected: - bool resolveLocal( const char *serv, const char *proto ) { + protected: + bool resolveLocal(const char *serv, const char *proto) { SockAddrUnix *addr = new SockAddrUnix; mLocalAddr = addr; - return( addr->resolve( serv, proto ) ); + return addr->resolve(serv, proto); } - bool resolveRemote( const char *path, const char *proto ) { + bool resolveRemote(const char *path, const char *proto) { SockAddrUnix *addr = new SockAddrUnix; mRemoteAddr = addr; - return( addr->resolve( path, proto ) ); + return addr->resolve(path, proto); } -protected: - bool bind( const char *path ) { - if ( !UnixSocket::resolveLocal( path, getProtocol() ) ) - return( false ); - return( Socket::bind() ); + bool bind(const char *path) { + if (!UnixSocket::resolveLocal(path, getProtocol())) + return false; + return Socket::bind(); } - bool connect( const char *path ) { - if ( !UnixSocket::resolveRemote( path, getProtocol() ) ) - return( false ); - return( Socket::connect() ); + bool connect(const char *path) { + if (!UnixSocket::resolveRemote(path, getProtocol())) + return false; + return Socket::connect(); } }; class UdpSocket : virtual public Socket { -public: - int getType() const { - return( SOCK_DGRAM ); - } - const char *getProtocol() const { - return( "udp" ); + public: + int getType() const override { return SOCK_DGRAM; } + const char *getProtocol() const override { return "udp"; } + + virtual int sendto(const void *msg, int len, const SockAddr *addr = nullptr) const { + ssize_t nBytes = ::sendto(mSd, msg, len, 0, addr ? addr->getAddr() : nullptr, addr ? addr->getAddrSize() : 0); + if (nBytes < 0) { + Debug(1, "Sendto of %d bytes on sd %d failed: %s", len, mSd, strerror(errno)); + } + return nBytes; } -public: - virtual int sendto( const void *msg, int len, const SockAddr *addr=nullptr ) const { - ssize_t nBytes = ::sendto( mSd, msg, len, 0, addr?addr->getAddr():nullptr, addr?addr->getAddrSize():0 ); - if ( nBytes < 0 ) - Debug( 1, "Sendto of %d bytes on sd %d failed: %s", len, mSd, strerror(errno) ); - return( nBytes ); - } - virtual int recvfrom( void *msg, int len, SockAddr *addr=0 ) const { + virtual int recvfrom(void *msg, int len, SockAddr *addr = nullptr) const { ssize_t nBytes = 0; - if ( addr ) { - struct sockaddr sockAddr; + if (addr) { + sockaddr sockAddr = {}; socklen_t sockLen; - nBytes = ::recvfrom( mSd, msg, len, 0, &sockAddr, &sockLen ); - if ( nBytes < 0 ) { - Debug( 1, "Recvfrom of %d bytes max on sd %d (with address) failed: %s", len, mSd, strerror(errno) ); + nBytes = ::recvfrom(mSd, msg, len, 0, &sockAddr, &sockLen); + if (nBytes < 0) { + Debug(1, "Recvfrom of %d bytes max on sd %d (with address) failed: %s", len, mSd, strerror(errno)); } } else { - nBytes = ::recvfrom( mSd, msg, len, 0, nullptr, 0 ); - if ( nBytes < 0 ) - Debug( 1, "Recvfrom of %d bytes max on sd %d (no address) failed: %s", len, mSd, strerror(errno) ); + nBytes = ::recvfrom(mSd, msg, len, 0, nullptr, nullptr); + if (nBytes < 0) { + Debug(1, "Recvfrom of %d bytes max on sd %d (no address) failed: %s", len, mSd, strerror(errno)); + } } - return( nBytes ); + return nBytes; } }; class UdpInetSocket : virtual public UdpSocket, virtual public InetSocket { -public: - bool bind( const char *host, const char *serv ) { - return( InetSocket::bind( host, serv ) ); - } - bool bind( const char *host, int port ) { - return( InetSocket::bind( host, port ) ); - } - bool bind( const char *serv ) { - return( InetSocket::bind( serv ) ); - } - bool bind( int port ) { - return( InetSocket::bind( port ) ); + public: + bool bind(const char *host, const char *serv) { + return InetSocket::bind(host, serv); } - bool connect( const char *host, const char *serv ) { - return( InetSocket::connect( host, serv ) ); + bool bind(const char *host, int port) { + return InetSocket::bind(host, port); } - bool connect( const char *host, int port ) { - return( InetSocket::connect( host, port ) ); + + bool bind(const char *serv) { + return InetSocket::bind(serv); + } + + bool bind(int port) { + return InetSocket::bind(port); + } + + bool connect(const char *host, const char *serv) { + return InetSocket::connect(host, serv); + } + + bool connect(const char *host, int port) { + return InetSocket::connect(host, port); } }; class UdpUnixSocket : virtual public UdpSocket, virtual public UnixSocket { -public: - bool bind( const char *path ) { - return( UnixSocket::bind( path ) ); + public: + bool bind(const char *path) { + return UnixSocket::bind(path); } - bool connect( const char *path ) { - return( UnixSocket::connect( path ) ); + bool connect(const char *path) { + return UnixSocket::connect(path); } }; class UdpInetClient : public UdpInetSocket { -public: - bool connect( const char *host, const char *serv ) { - return( UdpInetSocket::connect( host, serv ) ); + public: + bool connect(const char *host, const char *serv) { + return UdpInetSocket::connect(host, serv); } - bool connect( const char *host, int port ) { - return( UdpInetSocket::connect( host, port ) ); + + bool connect(const char *host, int port) { + return UdpInetSocket::connect(host, port); } }; class UdpUnixClient : public UdpUnixSocket { -public: - bool bind( const char *path ) { - return( UdpUnixSocket::bind( path ) ); + public: + bool bind(const char *path) { + return UdpUnixSocket::bind(path); } -public: - bool connect( const char *path ) { - return( UdpUnixSocket::connect( path) ); + bool connect(const char *path) { + return UdpUnixSocket::connect(path); } }; class UdpInetServer : public UdpInetSocket { -public: - bool bind( const char *host, const char *serv ) { - return( UdpInetSocket::bind( host, serv ) ); - } - bool bind( const char *host, int port ) { - return( UdpInetSocket::bind( host, port ) ); - } - bool bind( const char *serv ) { - return( UdpInetSocket::bind( serv ) ); - } - bool bind( int port ) { - return( UdpInetSocket::bind( port ) ); + public: + bool bind(const char *host, const char *serv) { + return UdpInetSocket::bind(host, serv); } -protected: - bool connect( const char *host, const char *serv ) { - return( UdpInetSocket::connect( host, serv ) ); + bool bind(const char *host, int port) { + return UdpInetSocket::bind(host, port); } - bool connect( const char *host, int port ) { - return( UdpInetSocket::connect( host, port ) ); + + bool bind(const char *serv) { + return UdpInetSocket::bind(serv); + } + + bool bind(int port) { + return UdpInetSocket::bind(port); + } + + protected: + bool connect(const char *host, const char *serv) { + return UdpInetSocket::connect(host, serv); + } + + bool connect(const char *host, int port) { + return UdpInetSocket::connect(host, port); } }; class UdpUnixServer : public UdpUnixSocket { -public: - bool bind( const char *path ) { - return( UdpUnixSocket::bind( path ) ); + public: + bool bind(const char *path) { + return UdpUnixSocket::bind(path); } -protected: - bool connect( const char *path ) { - return( UdpUnixSocket::connect( path ) ); + protected: + bool connect(const char *path) { + return UdpUnixSocket::connect(path); } }; class TcpSocket : virtual public Socket { -public: - TcpSocket() { - } - TcpSocket( const TcpSocket &socket, int newSd ) : Socket( socket, newSd ) { - } + public: + TcpSocket() = default; + TcpSocket(const TcpSocket &socket, int newSd) : Socket(socket, newSd) {} -public: - int getType() const { - return( SOCK_STREAM ); - } - const char *getProtocol() const { - return( "tcp" ); - } + int getType() const override { return SOCK_STREAM; } + const char *getProtocol() const override { return "tcp"; } }; class TcpInetSocket : virtual public TcpSocket, virtual public InetSocket { -public: - TcpInetSocket() { - } - TcpInetSocket( const TcpInetSocket &socket, int newSd ) : TcpSocket( socket, newSd ) { - } + public: + TcpInetSocket() = default; + TcpInetSocket(const TcpInetSocket &socket, int newSd) + : TcpSocket(socket, newSd) {} }; class TcpUnixSocket : virtual public TcpSocket, virtual public UnixSocket { -public: - TcpUnixSocket() { - } - TcpUnixSocket( const TcpUnixSocket &socket, int newSd ) : TcpSocket( socket, newSd ) { - } + public: + TcpUnixSocket() = default; + TcpUnixSocket(const TcpUnixSocket &socket, int newSd) + : TcpSocket(socket, newSd) {} }; class TcpInetClient : public TcpInetSocket { -public: - bool connect( const char *host, const char *serv ) { - return( TcpInetSocket::connect( host, serv ) ); + public: + bool connect(const char *host, const char *serv) { + return TcpInetSocket::connect(host, serv); } - bool connect( const char *host, int port ) { - return( TcpInetSocket::connect( host, port ) ); + + bool connect(const char *host, int port) { + return TcpInetSocket::connect(host, port); } }; class TcpUnixClient : public TcpUnixSocket { -public: - bool connect( const char *path ) { - return( TcpUnixSocket::connect( path) ); - } + public: + bool connect(const char *path) { return TcpUnixSocket::connect(path); } }; class TcpInetServer : public TcpInetSocket { -public: - bool bind( int port ) { - return( TcpInetSocket::bind( port ) ); - } + public: + bool bind(int port) { return TcpInetSocket::bind(port); } -public: - bool isListening() const { return( Socket::isListening() ); } - bool listen(); - bool accept(); - bool accept( TcpInetSocket *&newSocket ); + bool isListening() const override { return Socket::isListening(); } + bool listen() override; + bool accept() override; + bool accept(TcpInetSocket *&newSocket); }; class TcpUnixServer : public TcpUnixSocket { -public: - bool bind( const char *path ) { - return( TcpUnixSocket::bind( path ) ); - } + public: + bool bind(const char *path) { return TcpUnixSocket::bind(path); } -public: - bool isListening() const { return( Socket::isListening() ); } - bool listen(); - bool accept(); - bool accept( TcpUnixSocket *&newSocket ); + bool isListening() const override { return Socket::isListening(); } + bool listen() override; + bool accept() override; + bool accept(TcpUnixSocket *&newSocket); }; class Select { -public: + public: typedef std::set CommsSet; typedef std::vector CommsList; -protected: - CommsSet mReaders; - CommsSet mWriters; - CommsList mReadable; - CommsList mWriteable; - bool mHasTimeout; - struct timeval mTimeout; - int mMaxFd; + Select() : mHasTimeout(false), mMaxFd(-1) {} + explicit Select(timeval timeout) : mMaxFd(-1) { setTimeout(timeout); } + explicit Select(int timeout) : mMaxFd(-1) { setTimeout(timeout); } + explicit Select(double timeout) : mMaxFd(-1) { setTimeout(timeout); } -public: - Select(); - explicit Select( struct timeval timeout ); - explicit Select( int timeout ); - explicit Select( double timeout ); - - void setTimeout( int timeout ); - void setTimeout( double timeout ); - void setTimeout( struct timeval timeout ); + void setTimeout(int timeout); + void setTimeout(double timeout); + void setTimeout(timeval timeout); void clearTimeout(); void calcMaxFd(); - bool addReader( CommsBase *comms ); - bool deleteReader( CommsBase *comms ); + bool addReader(CommsBase *comms); + bool deleteReader(CommsBase *comms); void clearReaders(); - bool addWriter( CommsBase *comms ); - bool deleteWriter( CommsBase *comms ); + bool addWriter(CommsBase *comms); + bool deleteWriter(CommsBase *comms); void clearWriters(); int wait(); - const CommsList &getReadable() const; - const CommsList &getWriteable() const; + const CommsList &getReadable() const { return mReadable; } + const CommsList &getWriteable() const { return mWriteable; } + + protected: + CommsSet mReaders; + CommsSet mWriters; + CommsList mReadable; + CommsList mWriteable; + bool mHasTimeout; + timeval mTimeout; + int mMaxFd; }; +} + #endif // ZM_COMMS_H diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 015095472..97e2f8c2a 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -15,95 +15,97 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// + +#include "zm_config.h" -#include "zm.h" #include "zm_db.h" - -#include -#include -#include -#include +#include "zm_logger.h" +#include "zm_utils.h" +#include +#include #include #include -#include "zm_utils.h" - // Note that Error and Debug calls won't actually go anywhere unless you // set the relevant ENV vars because the logger gets it's setting from the // config. -void zmLoadConfig() { - +void zmLoadStaticConfig() { // Process name, value pairs from the main config file first process_configfile(ZM_CONFIG); // Search for user created config files. If one or more are found then // update the Config hash with those values - DIR* configSubFolder = opendir(ZM_CONFIG_SUBDIR); - if ( configSubFolder ) { // subfolder exists and is readable + DIR *configSubFolder = opendir(ZM_CONFIG_SUBDIR); + if (configSubFolder) { // subfolder exists and is readable char glob_pattern[PATH_MAX] = ""; snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.conf", ZM_CONFIG_SUBDIR); glob_t pglob; - int glob_status = glob(glob_pattern, 0, 0, &pglob); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { + int glob_status = glob(glob_pattern, 0, nullptr, &pglob); + if (glob_status != 0) { + if (glob_status < 0) { Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); } else { Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); } } else { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + for (unsigned int i = 0; i < pglob.gl_pathc; i++) { process_configfile(pglob.gl_pathv[i]); } } globfree(&pglob); closedir(configSubFolder); } +} - if ( !zmDbConnect() ) { - Fatal("Can't connect to db. Can't continue."); +void zmLoadDBConfig() { + if (!zmDbConnected) { + Fatal("Not connected to the database. Can't continue."); } config.Load(); config.Assign(); // Populate the server config entries - if ( !staticConfig.SERVER_ID ) { - if ( !staticConfig.SERVER_NAME.empty() ) { + if (!staticConfig.SERVER_ID) { + if (!staticConfig.SERVER_NAME.empty()) { Debug(1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str()); std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", - staticConfig.SERVER_NAME.c_str()); + staticConfig.SERVER_NAME.c_str()); zmDbRow dbrow; - if ( dbrow.fetch(sql.c_str()) ) { + if (dbrow.fetch(sql.c_str())) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str()); } } // end if has SERVER_NAME - } else if ( staticConfig.SERVER_NAME.empty() ) { + } else if (staticConfig.SERVER_NAME.empty()) { Debug(1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID); std::string sql = stringtf("SELECT `Name` FROM `Servers` WHERE `Id`='%d'", staticConfig.SERVER_ID); - + zmDbRow dbrow; - if ( dbrow.fetch(sql.c_str()) ) { + if (dbrow.fetch(sql.c_str())) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID); } - if ( staticConfig.SERVER_ID ) { - Debug(3, "Multi-server configuration detected. Server is %d.", staticConfig.SERVER_ID); - } else { - Debug(3, "Single server configuration assumed because no Server ID or Name was specified."); - } + if (staticConfig.SERVER_ID) { + Debug(3, "Multi-server configuration detected. Server is %d.", staticConfig.SERVER_ID); + } else { + Debug(3, "Single server configuration assumed because no Server ID or Name was specified."); + } } - snprintf(staticConfig.capture_file_format, sizeof(staticConfig.capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits); - snprintf(staticConfig.analyse_file_format, sizeof(staticConfig.analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits); - snprintf(staticConfig.general_file_format, sizeof(staticConfig.general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits); + snprintf(staticConfig.capture_file_format, sizeof(staticConfig.capture_file_format), "%%s/%%0%dd-capture.jpg", + config.event_image_digits); + snprintf(staticConfig.analyse_file_format, sizeof(staticConfig.analyse_file_format), "%%s/%%0%dd-analyse.jpg", + config.event_image_digits); + snprintf(staticConfig.general_file_format, sizeof(staticConfig.general_file_format), "%%s/%%0%dd-%%s", + config.event_image_digits); snprintf(staticConfig.video_file_format, sizeof(staticConfig.video_file_format), "%%s/%%s"); } @@ -324,10 +326,7 @@ const char *ConfigItem::StringValue() const { return cfg_value.string_value; } -Config::Config() { - n_items = 0; - items = 0; -} +Config::Config() : n_items(0), items(nullptr) { } Config::~Config() { if ( items ) { @@ -341,19 +340,11 @@ Config::~Config() { } void Config::Load() { - static char sql[ZM_SQL_SML_BUFSIZ]; - - strncpy(sql, "SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`", sizeof(sql) ); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + MYSQL_RES *result = zmDbFetch("SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`"); + if (!result) { + exit(-1); } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } n_items = mysql_num_rows(result); if ( n_items <= ZM_MAX_CFG_ID ) { @@ -362,7 +353,7 @@ void Config::Load() { } items = new ConfigItem *[n_items]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { items[i] = new ConfigItem(dbrow[0], dbrow[1], dbrow[2]); } mysql_free_result(result); diff --git a/src/zm_config.h b/src/zm_config.h index d348f38e9..daea8e14c 100644 --- a/src/zm_config.h +++ b/src/zm_config.h @@ -20,16 +20,15 @@ #ifndef ZM_CONFIG_H #define ZM_CONFIG_H +#include "config.h" +#include "zm_config_data.h" +#include "zm_config_defines.h" +#include + #if !defined(PATH_MAX) #define PATH_MAX 1024 #endif -#include "config.h" -#include "zm_config_defines.h" -#include "zm_config_data.h" - -#include - #ifdef HAVE_LIBAVFORMAT #define ZM_HAS_FFMPEG 1 #endif // HAVE_LIBAVFORMAT @@ -54,7 +53,8 @@ #define ZM_SAMPLE_RATE int(1000000/ZM_MAX_FPS) // A general nyquist sample frequency for delays etc #define ZM_SUSPENDED_RATE int(1000000/4) // A slower rate for when disabled etc -extern void zmLoadConfig(); +void zmLoadStaticConfig(); +void zmLoadDBConfig(); extern void process_configfile(char const *configFile); @@ -112,8 +112,9 @@ public: double DecimalValue() const; const char *StringValue() const; - ConfigItem &operator=(const ConfigItem item) { - Copy(item);return *this; + ConfigItem &operator=(const ConfigItem &item) { + Copy(item); + return *this; } inline operator bool() const { return BooleanValue(); diff --git a/src/zm_coord.cpp b/src/zm_coord.cpp index df9bc0a87..0b7ab0e71 100644 --- a/src/zm_coord.cpp +++ b/src/zm_coord.cpp @@ -17,7 +17,6 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_coord.h" // This section deliberately left blank diff --git a/src/zm_coord.h b/src/zm_coord.h index c6234fb1c..b7e8fd046 100644 --- a/src/zm_coord.h +++ b/src/zm_coord.h @@ -20,7 +20,7 @@ #ifndef ZM_COORD_H #define ZM_COORD_H -#include "zm.h" +#include "zm_define.h" // // Class used for storing an x,y pair, i.e. a coordinate @@ -38,22 +38,22 @@ public: y = coord.y; return *this; } - inline int &X() { return( x ); } - inline const int &X() const { return( x ); } - inline int &Y() { return( y ); } - inline const int &Y() const { return( y ); } + inline int &X(int p_x) { x=p_x; return x; } + inline const int &X() const { return x; } + inline int &Y(int p_y) { y=p_y; return y; } + inline const int &Y() const { return y; } inline static Coord Range( const Coord &coord1, const Coord &coord2 ) { Coord result( (coord1.x-coord2.x)+1, (coord1.y-coord2.y)+1 ); - return( result ); + return result; } - inline bool operator==( const Coord &coord ) { return( x == coord.x && y == coord.y ); } - inline bool operator!=( const Coord &coord ) { return( x != coord.x || y != coord.y ); } - inline bool operator>( const Coord &coord ) { return( x > coord.x && y > coord.y ); } - inline bool operator>=( const Coord &coord ) { return( !(operator<(coord)) ); } - inline bool operator<( const Coord &coord ) { return( x < coord.x && y < coord.y ); } - inline bool operator<=( const Coord &coord ) { return( !(operator>(coord)) ); } + inline bool operator==( const Coord &coord ) const { return( x == coord.x && y == coord.y ); } + inline bool operator!=( const Coord &coord ) const { return( x != coord.x || y != coord.y ); } + inline bool operator>( const Coord &coord ) const { return( x > coord.x && y > coord.y ); } + inline bool operator>=( const Coord &coord ) const { return( !(operator<(coord)) ); } + inline bool operator<( const Coord &coord ) const { return( x < coord.x && y < coord.y ); } + inline bool operator<=( const Coord &coord ) const { return( !(operator>(coord)) ); } inline Coord &operator+=( const Coord &coord ) { x += coord.x; y += coord.y; return( *this ); } inline Coord &operator-=( const Coord &coord ) { x -= coord.x; y -= coord.y; return( *this ); } diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 8e42b3b3c..583f5d8c2 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,19 +1,22 @@ -#include "zm.h" #include "zm_crypt.h" + +#include "zm_logger.h" #include "BCrypt.hpp" +#include +#include + #if HAVE_LIBJWT #include #else -#include "jwt_cpp.h" +#include #endif -#include + #if HAVE_LIBCRYPTO #include #elif HAVE_GNUTLS_GNUTLS_H #include #include #endif -#include // returns username if valid, "" if not #if HAVE_LIBJWT @@ -24,38 +27,39 @@ std::pair verifyToken(std::string jwt_token_str, std jwt_t *jwt = nullptr; err = jwt_new(&jwt); - if( err ) { + if (err) { Error("Unable to Allocate JWT object"); return std::make_pair("", 0); } err = jwt_set_alg(jwt, JWT_ALG_HS256, (const unsigned char*)key.c_str(), key.length()); - if( err ) { + if (err) { jwt_free(jwt); Error("Error setting Algorithm for JWT decode"); return std::make_pair("", 0); } - err = jwt_decode(&jwt, jwt_token_str.c_str(), nullptr, 0); - if( err ) { + err = jwt_decode(&jwt, jwt_token_str.c_str(), + reinterpret_cast(key.c_str()), key.length()); + if (err) { jwt_free(jwt); Error("Could not decode JWT"); return std::make_pair("", 0); } const char *c_type = jwt_get_grant(jwt, (const char*)"type"); - if ( !c_type ) { + if (!c_type) { jwt_free(jwt); Error("Missing token type. This should not happen"); return std::make_pair("", 0); - } else if ( std::string(c_type) != "access" ) { + } else if (std::string(c_type) != "access") { jwt_free(jwt); Error("Only access tokens are allowed. Please do not use refresh tokens"); return std::make_pair("", 0); } const char *c_username = jwt_get_grant(jwt, (const char*)"user"); - if( !c_username ) { + if (!c_username) { jwt_free(jwt); Error("User not found in claim"); return std::make_pair("", 0); @@ -65,7 +69,7 @@ std::pair verifyToken(std::string jwt_token_str, std Debug(1, "Got %s as user claim from token", username.c_str()); token_issued_at = (unsigned int)jwt_get_grant_int(jwt, "iat"); - if ( errno == ENOENT ) { + if (errno == ENOENT) { jwt_free(jwt); Error("IAT not found in claim. This should not happen"); return std::make_pair("", 0); @@ -90,26 +94,27 @@ std::pair verifyToken(std::string jwt_token_str, std verifier.verify(decoded); // make sure it has fields we need - if ( decoded.has_payload_claim("type") ) { + if (decoded.has_payload_claim("type")) { std::string type = decoded.get_payload_claim("type").as_string(); - if ( type != "access" ) { + if (type != "access") { Error("Only access tokens are allowed. Please do not use refresh tokens"); return std::make_pair("", 0); } } else { // something is wrong. All ZM tokens have type Error("Missing token type. This should not happen"); - return std::make_pair("",0); + return std::make_pair("", 0); } - if ( decoded.has_payload_claim("user") ) { - username = decoded.get_payload_claim("user").as_string(); + + if (decoded.has_payload_claim("user")) { + username = decoded.get_payload_claim("user").as_string(); Debug(1, "Got %s as user claim from token", username.c_str()); } else { Error("User not found in claim"); return std::make_pair("", 0); } - if ( decoded.has_payload_claim("iat") ) { + if (decoded.has_payload_claim("iat")) { token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_int()); Debug(1, "Got IAT token=%u", token_issued_at); } else { @@ -117,13 +122,13 @@ std::pair verifyToken(std::string jwt_token_str, std return std::make_pair("", 0); } } // try - catch ( const std::exception &e ) { - Error("Unable to verify token: %s", e.what()); - return std::make_pair("", 0); + catch (const std::exception &e) { + Error("Unable to verify token: %s", e.what()); + return std::make_pair("", 0); } catch (...) { - Error("unknown exception"); - return std::make_pair("", 0); + Error("unknown exception"); + return std::make_pair("", 0); } return std::make_pair(username, token_issued_at); } @@ -143,11 +148,10 @@ bool verifyPassword(const char *username, const char *input_password, const char #ifndef SHA_DIGEST_LENGTH #define SHA_DIGEST_LENGTH 20 #endif - - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; #if HAVE_LIBCRYPTO + unsigned char digest_interim[SHA_DIGEST_LENGTH]; + unsigned char digest_final[SHA_DIGEST_LENGTH]; SHA_CTX ctx1, ctx2; //get first iteration @@ -158,8 +162,10 @@ bool verifyPassword(const char *username, const char *input_password, const char //2nd iteration SHA1_Init(&ctx2); SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); - SHA1_Final (digest_final, &ctx2); + SHA1_Final(digest_final, &ctx2); #elif HAVE_GNUTLS_GNUTLS_H + unsigned char digest_interim[SHA_DIGEST_LENGTH]; + unsigned char digest_final[SHA_DIGEST_LENGTH]; //get first iteration gnutls_hash_fast(GNUTLS_DIG_SHA1, input_password, strlen(input_password), digest_interim); //2nd iteration @@ -188,7 +194,6 @@ bool verifyPassword(const char *username, const char *input_password, const char // BCRYPT Debug(1, "%s is using a bcrypt encoded password", username); BCrypt bcrypt; - std::string input_hash = bcrypt.generateHash(std::string(input_password)); password_correct = bcrypt.validatePassword(std::string(input_password), std::string(db_password_hash)); } else if ( strncmp(db_password_hash, "-ZM-",4) == 0 ) { Error("Authentication failed - migration of password not complete. Please log into web console for this user and retry this operation"); diff --git a/src/zm_crypt.h b/src/zm_crypt.h index 340abc36c..15dbc9332 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -20,10 +20,8 @@ #ifndef ZM_CRYPT_H #define ZM_CRYPT_H - -#include - - +#include +#include bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash); diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 421863101..a107d78eb 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -17,12 +17,10 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include "zm.h" - #include "zm_curl_camera.h" -#include "zm_packetqueue.h" +#include "zm_packet.h" +#include #if HAVE_LIBCURL @@ -48,15 +46,19 @@ size_t content_type_match_len; void bind_libcurl_symbols() { - if(curl_lib) + if (curl_lib) return; curl_lib = dlopen("libcurl.so", RTLD_LAZY | RTLD_GLOBAL); - if(!curl_lib) - curl_lib = dlopen("libcurl-gnutls.so.4", RTLD_LAZY | RTLD_GLOBAL); if (!curl_lib) { - Error("Could not load libcurl: %s", dlerror()); - return; + curl_lib = dlopen("libcurl.so.4", RTLD_LAZY | RTLD_GLOBAL); + if (!curl_lib) { + curl_lib = dlopen("libcurl-gnutls.so.4", RTLD_LAZY | RTLD_GLOBAL); + if (!curl_lib) { + Error("Could not load libcurl: %s", dlerror()); + return; + } + } } // Load up all required symbols here @@ -71,8 +73,8 @@ void bind_libcurl_symbols() { *(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup"); } -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : - Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), +cURLCamera::cURLCamera( const Monitor* monitor, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( monitor, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) { @@ -98,7 +100,7 @@ void cURLCamera::Initialise() { /* cURL initialization */ CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); if(cRet != CURLE_OK) { - Error("libcurl initialization failed: ", (*curl_easy_strerror_f)(cRet)); + Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet)); dlclose(curl_lib); return; } @@ -163,7 +165,7 @@ int cURLCamera::PreCapture() { return( 0 ); } -int cURLCamera::Capture( Image &image ) { +int cURLCamera::Capture( ZMPacket &zm_packet ) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -192,7 +194,7 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); if ( nRet != 0 ) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); - return -20; + return -1; } } @@ -212,7 +214,7 @@ int cURLCamera::Capture( Image &image ) { } /* Find crlf start */ - crlf_start = memcspn(databuffer,"\r\n",databuffer.size()); + crlf_start = memcspn(reinterpret_cast(databuffer.head()),"\r\n",databuffer.size()); if ( crlf_start == databuffer.size() ) { /* Not found, wait for more data */ need_more_data = true; @@ -295,7 +297,7 @@ int cURLCamera::Capture( Image &image ) { need_more_data = true; } else { /* All good. decode the image */ - image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); + zm_packet.image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); frameComplete = true; } } @@ -305,7 +307,7 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); if(nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); - return -18; + return -1; } need_more_data = false; } @@ -315,7 +317,7 @@ int cURLCamera::Capture( Image &image ) { if (!single_offsets.empty()) { if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { /* Extract frame */ - image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); + zm_packet.image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); single_offsets.pop_front(); frameComplete = true; } else { @@ -329,7 +331,7 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex); if(nRet != 0) { Error("Failed waiting for request complete condition variable: %s",strerror(nRet)); - return -19; + return -1; } } } else { @@ -344,7 +346,7 @@ int cURLCamera::Capture( Image &image ) { unlock(); if(!frameComplete) - return -1; + return 0; return 1; } @@ -354,12 +356,6 @@ int cURLCamera::PostCapture() { return( 0 ); } -int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { - Error("Capture and Record not implemented for the cURL camera type"); - // Nothing to do here - return( 0 ); -} - size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { lock(); @@ -436,7 +432,7 @@ void* cURLCamera::thread_func() { /* Set URL */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str()); if(cRet != CURLE_OK) { - Error("Failed setting libcurl URL: %s", *(curl_easy_strerror_f)(cRet)); + Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet)); tRet = -52; return (void*)tRet; } @@ -472,7 +468,7 @@ void* cURLCamera::thread_func() { /* Progress callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0); if(cRet != CURLE_OK) { - Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); + Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -57; return (void*)tRet; } diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index 816937a65..bd2266d5f 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -20,16 +20,13 @@ #ifndef ZM_CURL_CAMERA_H #define ZM_CURL_CAMERA_H -#if HAVE_LIBCURL - -#include "zm_camera.h" -#include "zm_ffmpeg.h" +#include "zm_config.h" #include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" -#include "zm_signal.h" -#include +#include "zm_camera.h" #include +#include + +#if HAVE_LIBCURL #if HAVE_CURL_CURL_H #include @@ -64,22 +61,35 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + cURLCamera( + const Monitor* monitor, + const std::string &path, + const std::string &username, + const std::string &password, + unsigned int p_width, + unsigned int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); ~cURLCamera(); - const std::string &Path() const { return( mPath ); } - const std::string &Username() const { return( mUser ); } - const std::string &Password() const { return( mPass ); } + const std::string &Path() const { return mPath; } + const std::string &Username() const { return mUser; } + const std::string &Password() const { return mPass; } void Initialise(); void Terminate(); - int Close() { return 0; }; + int Close() override { return 0; }; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); + int PrimeCapture() override; + int PreCapture() override; + int Capture(ZMPacket &p)override; + int PostCapture()override ; size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 37c3cd0f7..b296c571b 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -16,15 +16,15 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - -#include -#include - -#include "zm.h" #include "zm_db.h" +#include "zm_logger.h" +#include "zm_signal.h" +#include + MYSQL dbconn; -RecursiveMutex db_mutex; +std::mutex db_mutex; +zmDbQueue dbQueue; bool zmDbConnected = false; @@ -40,87 +40,104 @@ bool zmDbConnect() { Error("Can't initialise database connection: %s", mysql_error(&dbconn)); return false; } + bool reconnect = 1; if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) ) Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn)); - if ( !staticConfig.DB_SSL_CA_CERT.empty() ) + + if ( !staticConfig.DB_SSL_CA_CERT.empty() ) { mysql_ssl_set(&dbconn, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), nullptr, nullptr); + } + std::string::size_type colonIndex = staticConfig.DB_HOST.find(":"); if ( colonIndex == std::string::npos ) { - if ( !mysql_real_connect(&dbconn, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, 0, nullptr, 0) ) { - Error( "Can't connect to server: %s", mysql_error(&dbconn)); + if ( !mysql_real_connect( + &dbconn, + staticConfig.DB_HOST.c_str(), + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, 0, nullptr, 0) ) { + Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } else { - std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); - std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 ); + std::string dbHost = staticConfig.DB_HOST.substr(0, colonIndex); + std::string dbPortOrSocket = staticConfig.DB_HOST.substr(colonIndex+1); if ( dbPortOrSocket[0] == '/' ) { - if ( !mysql_real_connect(&dbconn, nullptr, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, 0, dbPortOrSocket.c_str(), 0) ) { + if ( !mysql_real_connect( + &dbconn, + nullptr, + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, 0, dbPortOrSocket.c_str(), 0) ) { Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } else { - if ( !mysql_real_connect( &dbconn, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, atoi(dbPortOrSocket.c_str()), nullptr, 0 ) ) { - Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); + if ( !mysql_real_connect( + &dbconn, + dbHost.c_str(), + staticConfig.DB_USER.c_str(), + staticConfig.DB_PASS.c_str(), + nullptr, + atoi(dbPortOrSocket.c_str()), + nullptr, 0) ) { + Error("Can't connect to server: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } } } - if ( mysql_select_db( &dbconn, staticConfig.DB_NAME.c_str() ) ) { - Error( "Can't select database: %s", mysql_error( &dbconn ) ); + if ( mysql_select_db(&dbconn, staticConfig.DB_NAME.c_str()) ) { + Error("Can't select database: %s", mysql_error(&dbconn)); + mysql_close(&dbconn); return false; } + if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) { + Error("Can't set isolation level: %s", mysql_error(&dbconn)); + } zmDbConnected = true; return zmDbConnected; } void zmDbClose() { - if ( zmDbConnected ) { - db_mutex.lock(); + if (zmDbConnected) { + std::lock_guard lck(db_mutex); mysql_close(&dbconn); // mysql_init() call implicitly mysql_library_init() but // mysql_close() does not call mysql_library_end() - // We get segfaults and a hang when we call this. So just don't. - //mysql_library_end(); + mysql_library_end(); zmDbConnected = false; - db_mutex.unlock(); } } MYSQL_RES * zmDbFetch(const char * query) { - if ( !zmDbConnected ) { - Error("Not connected."); - return nullptr; - } - db_mutex.lock(); - // Might have been disconnected while we waited for the lock - if ( !zmDbConnected ) { - db_mutex.unlock(); + std::lock_guard lck(db_mutex); + if (!zmDbConnected) { Error("Not connected."); return nullptr; } - if ( mysql_query(&dbconn, query) ) { - db_mutex.unlock(); + if (mysql_query(&dbconn, query)) { Error("Can't run query: %s", mysql_error(&dbconn)); return nullptr; } - Debug(4, "Success running query: %s", query); MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { + if (!result) { Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query); } - db_mutex.unlock(); return result; } // end MYSQL_RES * zmDbFetch(const char * query); zmDbRow *zmDbFetchOne(const char *query) { zmDbRow *row = new zmDbRow(); - if ( row->fetch(query) ) { + if (row->fetch(query)) { return row; } delete row; @@ -129,10 +146,10 @@ zmDbRow *zmDbFetchOne(const char *query) { MYSQL_RES *zmDbRow::fetch(const char *query) { result_set = zmDbFetch(query); - if ( ! result_set ) return result_set; + if (!result_set) return result_set; int n_rows = mysql_num_rows(result_set); - if ( n_rows != 1 ) { + if (n_rows != 1) { Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query); mysql_free_result(result_set); result_set = nullptr; @@ -140,7 +157,7 @@ MYSQL_RES *zmDbRow::fetch(const char *query) { } row = mysql_fetch_row(result_set); - if ( ! row ) { + if (!row) { mysql_free_result(result_set); result_set = nullptr; Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn)); @@ -150,9 +167,100 @@ MYSQL_RES *zmDbRow::fetch(const char *query) { return result_set; } +int zmDbDo(const char *query) { + std::lock_guard lck(db_mutex); + if (!zmDbConnected) + return 0; + int rc; + while ((rc = mysql_query(&dbconn, query)) and !zm_terminate) { + Logger *logger = Logger::fetch(); + Logger::Level oldLevel = logger->databaseLevel(); + logger->databaseLevel(Logger::NOLOG); + Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + logger->databaseLevel(oldLevel); + if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) { + return rc; + } + } + Logger *logger = Logger::fetch(); + Logger::Level oldLevel = logger->databaseLevel(); + logger->databaseLevel(Logger::NOLOG); + + Debug(1, "Success running sql query %s", query); + logger->databaseLevel(oldLevel); + return 1; +} + +int zmDbDoInsert(const char *query) { + std::lock_guard lck(db_mutex); + if (!zmDbConnected) return 0; + int rc; + while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { + Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) + return 0; + } + int id = mysql_insert_id(&dbconn); + Debug(2, "Success running sql insert %s. Resulting id is %d", query, id); + return id; +} + +int zmDbDoUpdate(const char *query) { + std::lock_guard lck(db_mutex); + if (!zmDbConnected) return 0; + int rc; + while ( (rc = mysql_query(&dbconn, query)) and !zm_terminate) { + Error("Can't run query %s: %s", query, mysql_error(&dbconn)); + if ( (mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT) ) + return -rc; + } + int affected = mysql_affected_rows(&dbconn); + Debug(2, "Success running sql update %s. Rows modified %d", query, affected); + return affected; +} + zmDbRow::~zmDbRow() { - if ( result_set ) { + if (result_set) { mysql_free_result(result_set); result_set = nullptr; } + row = nullptr; +} + +zmDbQueue::zmDbQueue() : + mThread(&zmDbQueue::process, this), + mTerminate(false) +{ } + +zmDbQueue::~zmDbQueue() { + stop(); +} + +void zmDbQueue::stop() { + mTerminate = true; + mCondition.notify_all(); + if (mThread.joinable()) mThread.join(); +} + +void zmDbQueue::process() { + std::unique_lock lock(mMutex); + + while (!mTerminate and !zm_terminate) { + if (mQueue.empty()) { + mCondition.wait(lock); + } + if (!mQueue.empty()) { + std::string sql = mQueue.front(); + mQueue.pop(); + lock.unlock(); + zmDbDo(sql.c_str()); + lock.lock(); + } + } +} // end void zmDbQueue::process() + +void zmDbQueue::push(std::string &&sql) { + std::unique_lock lock(mMutex); + mQueue.push(std::move(sql)); + mCondition.notify_all(); } diff --git a/src/zm_db.h b/src/zm_db.h index 11fc9faaa..c243aa6d6 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -20,17 +20,38 @@ #ifndef ZM_DB_H #define ZM_DB_H +#include +#include #include -#include "zm_thread.h" +#include +#include +#include +#include + +class zmDbQueue { + private: + std::queue mQueue; + std::thread mThread; + std::mutex mMutex; + std::condition_variable mCondition; + bool mTerminate; + public: + zmDbQueue(); + ~zmDbQueue(); + void push(const char *sql) { return push(std::string(sql)); }; + void push(std::string &&sql); + void process(); + void stop(); +}; class zmDbRow { private: MYSQL_RES *result_set; MYSQL_ROW row; public: - zmDbRow() { result_set = nullptr; row = nullptr; }; - MYSQL_RES *fetch( const char *query ); - zmDbRow( MYSQL_RES *, MYSQL_ROW *row ); + zmDbRow() : result_set(nullptr), row(nullptr) { }; + MYSQL_RES *fetch(const char *query); + zmDbRow(MYSQL_RES *, MYSQL_ROW *row); ~zmDbRow(); MYSQL_ROW mysql_row() const { return row; }; @@ -41,12 +62,20 @@ class zmDbRow { }; extern MYSQL dbconn; -extern RecursiveMutex db_mutex; +extern std::mutex db_mutex; +extern zmDbQueue dbQueue; + +extern bool zmDbConnected; + +extern bool zmDbConnected; bool zmDbConnect(); void zmDbClose(); +int zmDbDo(const char *query); +int zmDbDoInsert(const char *query); +int zmDbDoUpdate(const char *query); -MYSQL_RES * zmDbFetch( const char *query ); -zmDbRow *zmDbFetchOne( const char *query ); +MYSQL_RES * zmDbFetch(const char *query); +zmDbRow *zmDbFetchOne(const char *query); #endif // ZM_DB_H diff --git a/src/zm_decoder_thread.cpp b/src/zm_decoder_thread.cpp new file mode 100644 index 000000000..d32a578c7 --- /dev/null +++ b/src/zm_decoder_thread.cpp @@ -0,0 +1,27 @@ +#include "zm_decoder_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" + +DecoderThread::DecoderThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { + thread_ = std::thread(&DecoderThread::Run, this); +} + +DecoderThread::~DecoderThread() { + Stop(); + if (thread_.joinable()) thread_.join(); +} + +void DecoderThread::Start() { + if (thread_.joinable()) thread_.join(); + terminate_ = false; + thread_ = std::thread(&DecoderThread::Run, this); +} +void DecoderThread::Run() { + Debug(2, "DecoderThread::Run() for %d", monitor_->Id()); + + while (!(terminate_ or zm_terminate)) { + monitor_->Decode(); + } +} diff --git a/src/zm_decoder_thread.h b/src/zm_decoder_thread.h new file mode 100644 index 000000000..62b2f0d23 --- /dev/null +++ b/src/zm_decoder_thread.h @@ -0,0 +1,28 @@ +#ifndef ZM_DECODER_THREAD_H +#define ZM_DECODER_THREAD_H + +#include +#include +#include + +class Monitor; + +class DecoderThread { + public: + explicit DecoderThread(Monitor *monitor); + ~DecoderThread(); + DecoderThread(DecoderThread &rhs) = delete; + DecoderThread(DecoderThread &&rhs) = delete; + + void Start(); + void Stop() { terminate_ = true; } + + private: + void Run(); + + Monitor *monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/src/zm_define.h b/src/zm_define.h new file mode 100644 index 000000000..45ace017e --- /dev/null +++ b/src/zm_define.h @@ -0,0 +1,52 @@ +/* + * This file is part of the ZoneMinder Project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef ZONEMINDER_SRC_ZM_DEFINE_H_ +#define ZONEMINDER_SRC_ZM_DEFINE_H_ + +// These macros have not been adopted by the C++11 standard. +// However glibc 2.17 (CentOS 7) still depends on them to provide the macros which are guarded by these defines. +#if !defined(__STDC_FORMAT_MACROS) +# define __STDC_FORMAT_MACROS +#endif +#if !defined(__STDC_CONSTANT_MACROS) +# define __STDC_CONSTANT_MACROS +#endif + +#include + +typedef std::int64_t int64; +typedef std::int32_t int32; +typedef std::int16_t int16; +typedef std::int8_t int8; +typedef std::uint64_t uint64; +typedef std::uint32_t uint32; +typedef std::uint16_t uint16; +typedef std::uint8_t uint8; + +#ifndef FALLTHROUGH +#if defined(__clang__) +#define FALLTHROUGH [[clang::fallthrough]] +#elif defined(__GNUC__) && __GNUC__ >= 7 +#define FALLTHROUGH [[gnu::fallthrough]] +#else +#define FALLTHROUGH +#endif +#endif + +#endif // ZONEMINDER_SRC_ZM_DEFINE_H_ diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 76156c3a5..103320536 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -1,5 +1,5 @@ // -// ZoneMinder Event Class Implementation, $Date$, $Revision$ +// ZoneMinder Event Class Implementation // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or @@ -17,77 +17,101 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_signal.h" #include "zm_event.h" + +#include "zm_camera.h" +#include "zm_db.h" +#include "zm_frame.h" +#include "zm_logger.h" #include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_videostore.h" + +#include +#include +#include +#include //#define USE_PREPARED_SQL 1 const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; +#define MAX_DB_FRAMES 100 int Event::pre_alarm_count = 0; -Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } }; +Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = {}; Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, - const StringSetMap &p_noteSetMap, - bool p_videoEvent ) : + const StringSetMap &p_noteSetMap + ) : + id(0), monitor(p_monitor), start_time(p_start_time), + end_time({0,0}), cause(p_cause), noteSetMap(p_noteSetMap), - videoEvent(p_videoEvent), - videowriter(nullptr) + frames(0), + alarm_frames(0), + alarm_frame_written(false), + tot_score(0), + max_score(0), + //path(""), + //snapshit_file(), + //alarm_file(""), + videoStore(nullptr), + //video_name(""), + //video_file(""), + last_db_frame(0), + have_video_keyframe(false), + //scheme + save_jpegs(0) { - std::string notes; createNotes(notes); - struct timeval now; - gettimeofday(&now, 0); + timeval now = {}; + gettimeofday(&now, nullptr); - bool untimedEvent = false; if ( !start_time.tv_sec ) { - untimedEvent = true; + Warning("Event has zero time, setting to now"); start_time = now; } else if ( start_time.tv_sec > now.tv_sec ) { - Error( - "StartTime in the future %u.%u > %u.%u", - start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec - ); + char buffer[26]; + char buffer_now[26]; + tm tm_info = {}; + + localtime_r(&start_time.tv_sec, &tm_info); + strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", &tm_info); + localtime_r(&now.tv_sec, &tm_info); + strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", &tm_info); + + Error("StartDateTime in the future starttime %ld.%06ld >? now %ld.%06ld difference %" PRIi64 "\nstarttime: %s\nnow: %s", + start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec, + static_cast(now.tv_sec - start_time.tv_sec), + buffer, buffer_now); start_time = now; } - Storage * storage = monitor->getStorage(); - scheme = storage->Scheme(); - unsigned int state_id = 0; - zmDbRow dbrow; - if ( dbrow.fetch("SELECT Id FROM States WHERE IsActive=1") ) { - state_id = atoi(dbrow[0]); + { + zmDbRow dbrow; + if (dbrow.fetch("SELECT Id FROM States WHERE IsActive=1")) { + state_id = atoi(dbrow[0]); + } } - char sql[ZM_SQL_MED_BUFSIZ]; - struct tm *stime = localtime(&start_time.tv_sec); - snprintf(sql, sizeof(sql), "INSERT INTO Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme ) VALUES ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )", + // Copy it in case opening the mp4 doesn't work we can set it to another value + save_jpegs = monitor->GetOptSaveJPEGs(); + Storage * storage = monitor->getStorage(); + + std::string sql = stringtf( + "INSERT INTO `Events` " + "( `MonitorId`, `StorageId`, `Name`, `StartDateTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )" + " VALUES " + "( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )", monitor->Id(), storage->Id(), start_time.tv_sec, @@ -97,99 +121,65 @@ Event::Event( notes.c_str(), state_id, monitor->getOrientation(), - videoEvent, - ( monitor->GetOptVideoWriter() != 0 ? "video.mp4" : "" ), - monitor->GetOptSaveJPEGs(), + 0, + "", + save_jpegs, storage->SchemeString().c_str() ); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event: %s. sql was (%s)", mysql_error(&dbconn), sql); - db_mutex.unlock(); - return; - } - id = mysql_insert_id(&dbconn); - db_mutex.unlock(); - if ( untimedEvent ) { - Warning("Event %d has zero time, setting to current", id); - } - end_time.tv_sec = 0; - frames = 0; - alarm_frames = 0; - tot_score = 0; - max_score = 0; - alarm_frame_written = false; + id = zmDbDoInsert(sql.c_str()); - std::string id_file; + if ( !SetPath(storage) ) { + // Try another + Warning("Failed creating event dir at %s", storage->Path()); - path = stringtf("%s/%d", storage->Path(), monitor->Id()); - // Try to make the Monitor Dir. Normally this would exist, but in odd cases might not. - if ( mkdir(path.c_str(), 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); - } + sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); + if ( monitor->ServerId() ) + sql += stringtf(" AND ServerId=%u", monitor->ServerId()); - if ( storage->Scheme() == Storage::DEEP ) { + Debug(1, "%s", sql.c_str()); + storage = nullptr; - int dt_parts[6]; - dt_parts[0] = stime->tm_year-100; - dt_parts[1] = stime->tm_mon+1; - dt_parts[2] = stime->tm_mday; - dt_parts[3] = stime->tm_hour; - dt_parts[4] = stime->tm_min; - dt_parts[5] = stime->tm_sec; + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if ( result ) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + storage = new Storage(atoi(dbrow[0])); + if ( SetPath(storage) ) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + if ( !storage ) { + Info("No valid local storage area found. Trying all other areas."); + // Try remote + sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; + if ( monitor->ServerId() ) + sql += stringtf(" OR ServerId != %u", monitor->ServerId()); - std::string date_path; - std::string time_path; - - for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) { - path += stringtf("/%02d", dt_parts[i]); - - if ( mkdir(path.c_str(), 0755) ) { - // FIXME This should not be fatal. Should probably move to a different storage area. - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + result = zmDbFetch(sql.c_str()); + if ( result ) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + storage = new Storage(atoi(dbrow[0])); + if ( SetPath(storage) ) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; } - if ( i == 2 ) - date_path = path; } - time_path = stringtf("%02d/%02d/%02d", stime->tm_hour, stime->tm_min, stime->tm_sec); - - // Create event id symlink - id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id); - if ( symlink(time_path.c_str(), id_file.c_str()) < 0 ) - Error("Can't symlink %s -> %s: %s", id_file.c_str(), time_path.c_str(), strerror(errno)); - } else if ( storage->Scheme() == Storage::MEDIUM ) { - path += stringtf("/%04d-%02d-%02d", - stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday - ); - if ( mkdir(path.c_str(), 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + if ( !storage ) { + storage = new Storage(); + Warning("Failed to find a storage area to save events."); } - path += stringtf("/%" PRIu64, id); - if ( mkdir(path.c_str(), 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); - } - } else { - path += stringtf("/%" PRIu64, id); - if ( mkdir(path.c_str(), 0755) ) { - if ( errno != EEXIST ) - Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); - } - - // Create empty id tag file - id_file = stringtf("%s/.%" PRIu64, path.c_str(), id); - if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) { - fclose(id_fp); - } else { - Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno)); - } - } // deep storage or not - - last_db_frame = 0; + sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); + zmDbDo(sql.c_str()); + } // end if ! setPath(Storage) + Debug(1, "Using storage area at %s", path.c_str()); video_name = ""; @@ -199,102 +189,84 @@ Event::Event( /* Save as video */ if ( monitor->GetOptVideoWriter() != 0 ) { - video_name = stringtf("%" PRIu64 "-%s", id, "video.mp4"); - snprintf(sql, sizeof(sql), "UPDATE Events SET DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't update event: %s. sql was (%s)", mysql_error(&dbconn), sql); - db_mutex.unlock(); - return; + std::string container = monitor->OutputContainer(); + if ( container == "auto" || container == "" ) { + container = "mp4"; } + + video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str()); video_file = path + "/" + video_name; - Debug(1, "Writing video file to %s", video_file.c_str()); + Debug(1, "Writing video file to %s", video_file.c_str()); + videoStore = new VideoStore( + video_file.c_str(), + container.c_str(), + monitor->GetVideoStream(), + monitor->GetVideoCodecContext(), + ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), + ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), + monitor ); - /* X264 MP4 video writer */ - if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { -#if ZM_HAVE_VIDEOWRITER_X264MP4 - videowriter = new X264MP4Writer(video_file.c_str(), - monitor->Width(), - monitor->Height(), - monitor->Colours(), - monitor->SubpixelOrder(), - monitor->GetOptEncoderParamsVec()); -#else - Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); -#endif - - if ( videowriter != nullptr ) { - /* Open the video stream */ - int nRet = videowriter->Open(); - if ( nRet != 0 ) { - Error("Failed opening video stream"); - delete videowriter; - videowriter = nullptr; - } - } - } - } else { - /* No video object */ - videowriter = nullptr; - } + if ( !videoStore->open() ) { + Warning("Failed to open videostore, turning on jpegs"); + delete videoStore; + videoStore = nullptr; + if ( ! ( save_jpegs & 1 ) ) { + save_jpegs |= 1; // Turn on jpeg storage + sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id); + zmDbDo(sql.c_str()); + } + } else { + sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); + zmDbDo(sql.c_str()); + } + } // end if GetOptVideoWriter } // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) Event::~Event() { // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. /* Close the video file */ - if ( videowriter != nullptr ) { - int nRet = videowriter->Close(); - if ( nRet != 0 ) { - Error("Failed closing video stream"); - } - delete videowriter; - videowriter = nullptr; + if ( videoStore != nullptr ) { + Debug(4, "Deleting video store"); + delete videoStore; + videoStore = nullptr; } + // endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp. + if ( !end_time.tv_sec ) { + Warning("Empty endtime for event. Should not happen. Setting to now."); + gettimeofday(&end_time, nullptr); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL(delta_time, end_time, start_time, DT_PREC_2); - Debug(2, "start_time:%d.%d end_time%d.%d", start_time.tv_sec, start_time.tv_usec, end_time.tv_sec, end_time.tv_usec); + Debug(2, "start_time: %" PRIi64 ".% " PRIi64 " end_time: %" PRIi64 ".%" PRIi64, + static_cast(start_time.tv_sec), + static_cast(start_time.tv_usec), + static_cast(end_time.tv_sec), + static_cast(end_time.tv_usec)); -#if 0 // This closing frame has no image. There is no point in adding a db record for it, I think. ICON - if ( frames > last_db_frame ) { - frames ++; - Debug(1, "Adding closing frame %d to DB", frames); - frame_data.push(new Frame(id, frames, NORMAL, end_time, delta_time, 0)); - } -#endif - if ( frame_data.size() ) - WriteDbFrames(); - - // update frame deltas to refer to video start time which may be a few frames before event start - struct timeval video_offset = {0}; - struct timeval video_start_time = monitor->GetVideoWriterStartTime(); - if ( video_start_time.tv_sec > 0 ) { - timersub(&video_start_time, &start_time, &video_offset); - Debug(1, "Updating frames delta by %d sec %d usec", - video_offset.tv_sec, video_offset.tv_usec); - UpdateFramesDelta(video_offset.tv_sec + video_offset.tv_usec*1e-6); - } else { - Debug(3, "Video start_time %d sec %d usec not valid -- frame deltas not updated", - video_start_time.tv_sec, video_start_time.tv_usec); - } + if (frame_data.size()) WriteDbFrames(); // Should not be static because we might be multi-threaded char sql[ZM_SQL_LGE_BUFSIZ]; - snprintf(sql, sizeof(sql), - "UPDATE Events SET Name='%s%" PRIu64 "', EndTime = from_unixtime(%ld), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + snprintf(sql, sizeof(sql), + "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id); - db_mutex.lock(); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - db_mutex.unlock(); - Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); - sleep(1); - db_mutex.lock(); - } - db_mutex.unlock(); + if (!zmDbDoUpdate(sql)) { + // Name might have been changed during recording, so just do the update without changing the name. + snprintf(sql, sizeof(sql), + "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + end_time.tv_sec, + delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, + frames, alarm_frames, + tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, + id); + zmDbDoUpdate(sql); + } // end if no changed rows due to Name change during recording } // Event::~Event() void Event::createNotes(std::string ¬es) { @@ -315,7 +287,7 @@ bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, - bool alarm_frame) { + bool alarm_frame) const { int thisquality = (alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ? @@ -337,38 +309,12 @@ bool Event::WriteFrameImage( } return rc; -} +} // end Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) -bool Event::WriteFrameVideo( - const Image *image, - const struct timeval timestamp, - VideoWriter* videow) { - const Image* frameimg = image; - Image ts_image; - - /* Checking for invalid parameters */ - if ( videow == nullptr ) { - Error("NULL Video object"); +bool Event::WritePacket(ZMPacket &packet) { + + if ( videoStore->writePacket(&packet) < 0 ) return false; - } - - /* If the image does not contain a timestamp, add the timestamp */ - if ( !config.timestamp_on_capture ) { - ts_image = *image; - monitor->TimestampImage(&ts_image, ×tamp); - frameimg = &ts_image; - } - - /* Calculate delta time */ - struct DeltaTimeval delta_time3; - DELTA_TIMEVAL(delta_time3, timestamp, start_time, DT_PREC_3); - unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; - - /* Encode and write the frame */ - if ( videowriter->Encode(frameimg, timeMS) != 0 ) { - Error("Failed encoding video frame"); - } - return true; } // bool Event::WriteFrameVideo @@ -416,8 +362,7 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { std::string notes; createNotes(notes); - Debug(2, "Updating notes for event %d, '%s'", id, notes.c_str()); - static char sql[ZM_SQL_LGE_BUFSIZ]; + Debug(2, "Updating notes for event %" PRIu64 ", '%s'", id, notes.c_str()); #if USE_PREPARED_SQL static MYSQL_STMT *stmt = 0; @@ -456,7 +401,7 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { if ( mysql_stmt_bind_param(stmt, bind) ) { Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt)); } - } + } // end if ! stmt strncpy(notesStr, notes.c_str(), sizeof(notesStr)); @@ -464,211 +409,196 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { Error("Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt)); } #else + char sql[ZM_SQL_LGE_BUFSIZ]; static char escapedNotes[ZM_SQL_MED_BUFSIZ]; mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length()); snprintf(sql, sizeof(sql), "UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, escapedNotes, id); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + dbQueue.push(std::move(sql)); #endif } // end if update } // void Event::updateNotes(const StringSetMap &newNoteSetMap) -void Event::AddFrames(int n_frames, Image **images, struct timeval **timestamps) { - for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) { - AddFramesInternal(n_frames, i, images, timestamps); +void Event::AddPacket(ZMPacket *packet) { + + have_video_keyframe = have_video_keyframe || + ( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) && + ( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) ); + Debug(2, "have_video_keyframe %d codec_type %d == video? %d packet keyframe %d", + have_video_keyframe, packet->codec_type, (packet->codec_type == AVMEDIA_TYPE_VIDEO), packet->keyframe); + ZM_DUMP_PACKET(packet->packet, "Adding to event"); + if (videoStore) { + if (have_video_keyframe) { + videoStore->writePacket(packet); + } else { + Debug(2, "No video keyframe yet, not writing"); + } + //FIXME if it fails, we should write a jpeg } + if ((packet->codec_type == AVMEDIA_TYPE_VIDEO) or packet->image) + AddFrame(packet->image, *(packet->timestamp), packet->zone_stats, packet->score, packet->analysis_image); + end_time = *packet->timestamp; + return; } -void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) { - static char sql[ZM_SQL_LGE_BUFSIZ]; - strncpy(sql, "INSERT INTO `Frames` (`EventId`, `FrameId`, `TimeStamp`, `Delta`) VALUES ", sizeof(sql)); - int frameCount = 0; - for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { - if ( timestamps[i]->tv_sec <= 0 ) { - Debug(1, "Not adding pre-capture frame %d, zero or less than 0 timestamp", i); - continue; - } - - frames++; - - if ( monitor->GetOptSaveJPEGs() & 1 ) { - std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); - Debug(1, "Writing pre-capture frame %d", frames); - WriteFrameImage(images[i], *(timestamps[i]), event_file.c_str()); - } - //If this is the first frame, we should add a thumbnail to the event directory - // ICON: We are working through the pre-event frames so this snapshot won't - // neccessarily be of the motion. But some events are less than 10 frames, - // so I am changing this to 1, but we should overwrite it later with a better snapshot. - if ( frames == 1 ) { - WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str()); - } - - if ( videowriter != nullptr ) { - WriteFrameVideo(images[i], *(timestamps[i]), videowriter); - } - - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, *(timestamps[i]), start_time, DT_PREC_2); - // Delta is Decimal(8,2) so 6 integer digits and 2 decimal digits - if ( delta_time.sec > 999999 ) { - Warning("Invalid delta_time from_unixtime(%ld), %s%ld.%02ld", - timestamps[i]->tv_sec, - (delta_time.positive?"":"-"), - delta_time.sec, - delta_time.fsec); - delta_time.sec = 0; - } - - int sql_len = strlen(sql); - snprintf(sql+sql_len, sizeof(sql)-sql_len, "( %" PRIu64 ", %d, from_unixtime(%ld), %s%ld.%02ld ), ", - id, frames, timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec); - - frameCount++; - } // end foreach frame - - if ( frameCount ) { - Debug(1, "Adding %d/%d frames to DB", frameCount, n_frames); - *(sql+strlen(sql)-2) = '\0'; - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert frames: %s, sql was (%s)", mysql_error(&dbconn), sql); - } - db_mutex.unlock(); - last_db_frame = frames; - } else { - Debug(1, "No valid pre-capture frames to add"); - } -} // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) - void Event::WriteDbFrames() { - static char sql[ZM_SQL_LGE_BUFSIZ]; - char * sql_ptr = (char *)&sql; - sql_ptr += snprintf(sql, sizeof(sql), - "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) VALUES " - ); - while ( frame_data.size() ) { + std::string frame_insert_sql = "INSERT INTO `Frames` (`EventId`, `FrameId`, `Type`, `TimeStamp`, `Delta`, `Score`) VALUES "; + std::string stats_insert_sql = "INSERT INTO `Stats` (`EventId`, `FrameId`, `MonitorId`, `ZoneId`, " + "`PixelDiff`, `AlarmPixels`, `FilterPixels`, `BlobPixels`," + "`Blobs`,`MinBlobSize`, `MaxBlobSize`, " + "`MinX`, `MinY`, `MaxX`, `MaxY`,`Score`) VALUES "; + + Debug(1, "Inserting %zu frames", frame_data.size()); + while (frame_data.size()) { Frame *frame = frame_data.front(); frame_data.pop(); - sql_ptr += snprintf(sql_ptr, sizeof(sql)-(sql_ptr-(char *)&sql), "( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d ), ", - id, frame->frame_id, frame_type_names[frame->type], + frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d ),", + id, frame->frame_id, + frame_type_names[frame->type], frame->timestamp.tv_sec, - frame->delta.positive?"":"-", + frame->delta.positive ? "" : "-", frame->delta.sec, frame->delta.fsec, frame->score); + if (config.record_event_stats and frame->zone_stats.size()) { + for (ZoneStats &stats : frame->zone_stats) { + stats_insert_sql += stringtf("\n(%" PRIu64 ",%d,%u,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%u),", + id, frame->frame_id, + monitor->Id(), + stats.zone_id_, + stats.pixel_diff_, + stats.alarm_pixels_, + stats.alarm_filter_pixels_, + stats.alarm_blob_pixels_, + stats.alarm_blobs_, + stats.min_blob_size_, + stats.max_blob_size_, + stats.alarm_box_.LoX(), + stats.alarm_box_.LoY(), + stats.alarm_box_.HiX(), + stats.alarm_box_.HiY(), + stats.score_); + } // end foreach zone stats + } // end if recording stats delete frame; + } // end while frames + // The -1 is for the extra , added for values above + frame_insert_sql.erase(frame_insert_sql.size()-1); + //zmDbDo(frame_insert_sql.c_str()); + dbQueue.push(std::move(frame_insert_sql)); + if (stats_insert_sql.size() > 208) { + // The -1 is for the extra , added for values above + stats_insert_sql.erase(stats_insert_sql.size()-1); + //zmDbDo(stats_insert_sql); + dbQueue.push(std::move(stats_insert_sql)); } - *(sql_ptr-2) = '\0'; - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't insert frames: %s, sql was %s", mysql_error(&dbconn), sql); - return; - } - db_mutex.unlock(); } // end void Event::WriteDbFrames() -// Subtract an offset time from frames deltas to match with video start time -void Event::UpdateFramesDelta(double offset) { - char sql[ZM_SQL_MED_BUFSIZ]; - - if ( offset == 0.0 ) return; - // the table is set to auto update timestamp so we force it to keep current value - snprintf(sql, sizeof(sql), - "UPDATE Frames SET timestamp = timestamp, Delta = Delta - (%.4f) WHERE EventId = %" PRIu64, - offset, id); - - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't update frames: %s, sql was %s", mysql_error(&dbconn), sql); - return; - } - db_mutex.unlock(); - Info("Updating frames delta by %0.2f sec to match video file", offset); -} - -void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { - if ( !timestamp.tv_sec ) { - Debug(1, "Not adding new frame, zero timestamp"); +void Event::AddFrame( + Image *image, + struct timeval timestamp, + const std::vector &zone_stats, + int score, + Image *alarm_image) { + if (!timestamp.tv_sec) { + Warning("Not adding new frame, zero timestamp"); return; } frames++; + Monitor::State monitor_state = monitor->GetState(); + bool write_to_db = false; - FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); - // < 0 means no motion detection is being done. - if ( score < 0 ) - score = 0; + FrameType frame_type = ( ( score > 0 ) ? ALARM : ( + ( + ( monitor_state == Monitor::TAPE ) + and + ( config.bulk_frame_interval > 1 ) + and + ( ! (frames % config.bulk_frame_interval) ) + ) ? BULK : NORMAL + ) ); + Debug(1, "Have frame type %s from score(%d) state %d frames %d bulk frame interval %d and mod%d", + frame_type_names[frame_type], score, monitor->GetState(), frames, config.bulk_frame_interval, (frames % config.bulk_frame_interval)); - if ( monitor->GetOptSaveJPEGs() & 1 ) { - std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); - Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str()); - if ( !WriteFrameImage(image, timestamp, event_file.c_str()) ) { - Error("Failed to write frame image"); + if (score < 0) score = 0; + tot_score += score; + + if (image) { + if (save_jpegs & 1) { + std::string event_file = stringtf(staticConfig.capture_file_format, path.c_str(), frames); + Debug(1, "Writing capture frame %d to %s", frames, event_file.c_str()); + if (!WriteFrameImage(image, timestamp, event_file.c_str())) { + Error("Failed to write frame image"); + } + } // end if save_jpegs + + // If this is the first frame, we should add a thumbnail to the event directory + if ((frames == 1) || (score > (int)max_score)) { + write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. + WriteFrameImage(image, timestamp, snapshot_file.c_str()); } - } - // If this is the first frame, we should add a thumbnail to the event directory - if ( (frames == 1) || (score > (int)max_score) ) { - write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - WriteFrameImage(image, timestamp, snapshot_file.c_str()); - } + // We are writing an Alarm frame + if (frame_type == ALARM) { + // The first frame with a score will be the frame that alarmed the event + if (!alarm_frame_written) { + write_to_db = true; // OD processing will need it, so the db needs to know about it + alarm_frame_written = true; + WriteFrameImage(image, timestamp, alarm_file.c_str()); + } - // We are writing an Alarm frame - if ( frame_type == ALARM ) { - // The first frame with a score will be the frame that alarmed the event - if ( !alarm_frame_written ) { - write_to_db = true; // OD processing will need it, so the db needs to know about it - alarm_frame_written = true; - WriteFrameImage(image, timestamp, alarm_file.c_str()); - } - alarm_frames++; - - tot_score += score; - if ( score > (int)max_score ) - max_score = score; - - if ( alarm_image ) { - if ( monitor->GetOptSaveJPEGs() & 2 ) { + if (alarm_image and (save_jpegs & 2)) { std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames); Debug(1, "Writing analysis frame %d", frames); - if ( ! WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true) ) { + if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { Error("Failed to write analysis frame image"); } } - } - } // end if frame_type == ALARM + } // end if is an alarm frame + } else { + Debug(1, "No image"); + } // end if has image - if ( videowriter != nullptr ) { - WriteFrameVideo(image, timestamp, videowriter); - } + if (frame_type == ALARM) alarm_frames++; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); + bool db_frame = ( frame_type == BULK ) + or ( frame_type == ALARM ) + or ( frames == 1 ) + or ( score > (int)max_score ) + or ( monitor_state == Monitor::ALERT ) + or ( monitor_state == Monitor::ALARM ) + or ( monitor_state == Monitor::PREALARM ); - bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ; - if ( db_frame ) { - static char sql[ZM_SQL_MED_BUFSIZ]; + if (db_frame) { + + struct DeltaTimeval delta_time; + DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); + Debug(1, "Frame delta is %" PRIi64 ".%" PRIi64 " - %" PRIi64 ".%" PRIi64 " = %lu.%lu, score %u zone_stats.size %zu", + static_cast(start_time.tv_sec), static_cast(start_time.tv_usec), + static_cast(timestamp.tv_sec), static_cast(timestamp.tv_usec), + delta_time.sec, delta_time.fsec, + score, + zone_stats.size()); // The idea is to write out 1/sec - frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score)); - if ( write_to_db || ( monitor->get_fps() && (frame_data.size() > monitor->get_fps())) ) { - Debug(1, "Adding %d frames to DB because write_to_db:%d or frames > analysis fps %f", - frame_data.size(), write_to_db, monitor->get_fps()); + frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score, zone_stats)); + double fps = monitor->get_capture_fps(); + if ( write_to_db + or + ( frame_data.size() >= MAX_DB_FRAMES ) + or + ( frame_type == BULK ) + or + ( fps and (frame_data.size() > fps) ) + ) { + Debug(1, "Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)", + frame_data.size(), write_to_db, fps, (frame_type == BULK)); WriteDbFrames(); last_db_frame = frames; - Debug(1, "Adding %d frames to DB, done", frame_data.size()); - } - // We are writing a Bulk frame - if ( frame_type == BULK ) { + char sql[ZM_SQL_MED_BUFSIZ]; snprintf(sql, sizeof(sql), "UPDATE Events SET Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, ( delta_time.positive?"":"-" ), @@ -680,16 +610,89 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a max_score, id ); - db_mutex.lock(); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - Error("Can't update event: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - sleep(1); - db_mutex.lock(); - } - db_mutex.unlock(); + dbQueue.push(std::move(sql)); + } else { + Debug(1, "Not Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK", + frame_data.size(), write_to_db, fps); } // end if frame_type == BULK } // end if db_frame + if (score > (int)max_score) + max_score = score; end_time = timestamp; } // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) + +bool Event::SetPath(Storage *storage) { + scheme = storage->Scheme(); + + path = stringtf("%s/%d", storage->Path(), monitor->Id()); + // Try to make the Monitor Dir. Normally this would exist, but in odd cases might not. + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + + tm stime = {}; + localtime_r(&start_time.tv_sec, &stime); + if (scheme == Storage::DEEP) { + + int dt_parts[6]; + dt_parts[0] = stime.tm_year-100; + dt_parts[1] = stime.tm_mon+1; + dt_parts[2] = stime.tm_mday; + dt_parts[3] = stime.tm_hour; + dt_parts[4] = stime.tm_min; + dt_parts[5] = stime.tm_sec; + + std::string date_path; + std::string time_path; + + for (unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++) { + path += stringtf("/%02d", dt_parts[i]); + + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + if (i == 2) + date_path = path; + } + time_path = stringtf("%02d/%02d/%02d", stime.tm_hour, stime.tm_min, stime.tm_sec); + + // Create event id symlink + std::string id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id); + if (symlink(time_path.c_str(), id_file.c_str()) < 0) { + Error("Can't symlink %s -> %s: %s", id_file.c_str(), time_path.c_str(), strerror(errno)); + return false; + } + } else if (scheme == Storage::MEDIUM) { + path += stringtf("/%04d-%02d-%02d", + stime.tm_year+1900, stime.tm_mon+1, stime.tm_mday + ); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + path += stringtf("/%" PRIu64, id); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + } else { + path += stringtf("/%" PRIu64, id); + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { + Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); + return false; + } + + // Create empty id tag file + std::string id_file = stringtf("%s/.%" PRIu64, path.c_str(), id); + if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) { + fclose(id_fp); + } else { + Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno)); + return false; + } + } // deep storage or not + return true; +} // end bool Event::SetPath diff --git a/src/zm_event.h b/src/zm_event.h index 8c44af1f2..7d0a334c5 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -1,69 +1,58 @@ // // ZoneMinder Core Interfaces, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes -// +// // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// #ifndef ZM_EVENT_H #define ZM_EVENT_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "zm_config.h" +#include "zm_define.h" +#include "zm_storage.h" +#include "zm_zone.h" -#include #include #include +#include -#include "zm.h" -#include "zm_image.h" -#include "zm_stream.h" -#include "zm_video.h" -#include "zm_storage.h" - -class Zone; -class Monitor; class EventStream; +class Frame; +class Image; +class Monitor; +class VideoStore; +class ZMPacket; +class Zone; + +// Maximum number of prealarm frames that can be stored +#define MAX_PRE_ALARM_FRAMES 16 -#define MAX_PRE_ALARM_FRAMES 16 // Maximum number of prealarm frames that can be stored typedef uint64_t event_id_t; - typedef enum { NORMAL=0, BULK, ALARM } FrameType; -#include "zm_frame.h" // // Class describing events, i.e. captured periods of activity. // class Event { friend class EventStream; - protected: - static int sd; - - public: + public: typedef std::set StringSet; typedef std::map StringSetMap; - protected: + protected: static const char * frame_type_names[3]; struct PreAlarmData { @@ -83,78 +72,92 @@ class Event { struct timeval end_time; std::string cause; StringSetMap noteSetMap; - bool videoEvent; int frames; int alarm_frames; bool alarm_frame_written; unsigned int tot_score; unsigned int max_score; - std::string path; + std::string path; std::string snapshot_file; std::string alarm_file; + VideoStore *videoStore; - VideoWriter* videowriter; - FILE* timecodes_fd; std::string video_name; std::string video_file; - - std::string timecodes_name; - std::string timecodes_file; int last_db_frame; + bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe. Storage::Schemes scheme; + int save_jpegs; - void createNotes( std::string ¬es ); + void createNotes(std::string ¬es); - public: - static bool OpenFrameSocket( int ); - static bool ValidateFrameSocket( int ); + public: + static bool OpenFrameSocket(int); + static bool ValidateFrameSocket(int); - Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false ); + Event( + Monitor *p_monitor, + struct timeval p_start_time, + const std::string &p_cause, + const StringSetMap &p_noteSetMap + ); ~Event(); uint64_t Id() const { return id; } - const std::string &Cause() { return cause; } + const std::string &Cause() const { return cause; } int Frames() const { return frames; } int AlarmFrames() const { return alarm_frames; } const struct timeval &StartTime() const { return start_time; } const struct timeval &EndTime() const { return end_time; } - struct timeval &StartTime() { return start_time; } - struct timeval &EndTime() { return end_time; } - bool SendFrameImage( const Image *image, bool alarm_frame=false ); - bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); - bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ); + void AddPacket(ZMPacket *p); + bool WritePacket(ZMPacket &p); + bool SendFrameImage(const Image *image, bool alarm_frame=false); + bool WriteFrameImage( + Image *image, + struct timeval timestamp, + const char *event_file, + bool alarm_frame=false + ) const; - void updateNotes( const StringSetMap &stringSetMap ); + void updateNotes(const StringSetMap &stringSetMap); - void AddFrames( int n_frames, Image **images, struct timeval **timestamps ); - void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=nullptr ); + void AddFrame( + Image *image, + struct timeval timestamp, + const std::vector &stats, + int score=0, + Image *alarm_image=nullptr + ); - private: - void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); + private: void WriteDbFrames(); - void UpdateFramesDelta(double offset); + bool SetPath(Storage *storage); - public: - static const char *getSubPath( struct tm *time ) { + public: + static const char *getSubPath(tm time) { static char subpath[PATH_MAX] = ""; - snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); + snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", + time.tm_year-100, time.tm_mon+1, time.tm_mday, + time.tm_hour, time.tm_min, time.tm_sec); return subpath; } - static const char *getSubPath( time_t *time ) { - return Event::getSubPath( localtime( time ) ); + static const char *getSubPath(time_t *time) { + tm time_tm = {}; + localtime_r(time, &time_tm); + return Event::getSubPath(time_tm); } - const char* getEventFile(void) { + const char* getEventFile() const { return video_file.c_str(); } - public: static int PreAlarmCount() { return pre_alarm_count; } static void EmptyPreAlarmFrames() { +#if 0 while ( pre_alarm_count > 0 ) { int i = pre_alarm_count - 1; delete pre_alarm_data[i].image; @@ -165,18 +168,27 @@ class Event { } pre_alarm_count--; } +#endif pre_alarm_count = 0; } - static void AddPreAlarmFrame(Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=nullptr) { + static void AddPreAlarmFrame( + Image *image, + struct timeval timestamp, + int score=0, + Image *alarm_frame=nullptr + ) { +#if 0 pre_alarm_data[pre_alarm_count].image = new Image(*image); pre_alarm_data[pre_alarm_count].timestamp = timestamp; pre_alarm_data[pre_alarm_count].score = score; if ( alarm_frame ) { pre_alarm_data[pre_alarm_count].alarm_frame = new Image(*alarm_frame); } +#endif pre_alarm_count++; } void SavePreAlarmFrames() { +#if 0 for ( int i = 0; i < pre_alarm_count; i++ ) { AddFrame( pre_alarm_data[i].image, @@ -184,6 +196,7 @@ class Event { pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame); } +#endif EmptyPreAlarmFrames(); } }; diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index b9abf5e76..7a35f6892 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -1,5 +1,5 @@ // -// ZoneMinder Event Class Implementation, $Date$, $Revision$ +// ZoneMinder Event Stream Class Implementation // Copyright (C) 2001-2008 Philip Coombes // // This program is free software; you can redistribute it and/or @@ -16,48 +16,39 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" -#include "zm_event.h" +// #include "zm_eventstream.h" -#include "zm_storage.h" -#include "zm_monitor.h" +#include "zm_db.h" +#include "zm_image.h" +#include "zm_logger.h" #include "zm_sendfile.h" +#include "zm_signal.h" +#include "zm_storage.h" +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#endif + +const std::string EventStream::StreamMode_Strings[4] = { + "None", + "Single", + "All", + "Gapless" +}; bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { - static char sql[ZM_SQL_SML_BUFSIZ]; + std::string sql = stringtf("SELECT `Id` FROM `Events` WHERE " + "`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld " + "ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); - snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE " - "`MonitorId` = %d AND unix_timestamp(`EndTime`) > %ld " - "ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) + exit(-1); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } MYSQL_ROW dbrow = mysql_fetch_row(result); if ( mysql_errno(&dbconn) ) { @@ -80,25 +71,28 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); if ( event_data->frames[i].timestamp >= event_time ) { curr_frame_id = i+1; - Debug(3, "Set curr_stream_time:%.2f, curr_frame_id:%d", curr_stream_time, curr_frame_id); + Debug(3, "Set curr_stream_time:%.2f, curr_frame_id:%ld", curr_stream_time, curr_frame_id); break; } } // end foreach frame Debug(3, "Skipping %ld frames", event_data->frame_count); } else { - Warning("Requested an event time less than the start of the event. event_time %.2f < start_time %.2f", - event_time, event_data->start_time); + Warning("Requested an event time less than the start of the event. event_time %" PRIi64 " < start_time %" PRIi64, + static_cast(event_time), static_cast(event_data->start_time)); } } // end if have a start time return true; } // bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) -bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id) { +bool EventStream::loadInitialEventData( + uint64_t init_event_id, + unsigned int init_frame_id + ) { loadEventData(init_event_id); if ( init_frame_id ) { if ( init_frame_id >= event_data->frame_count ) { - Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count); + Error("Invalid frame id specified. %d > %lu", init_frame_id, event_data->frame_count); curr_stream_time = event_data->start_time; curr_frame_id = 1; } else { @@ -113,26 +107,19 @@ bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init } bool EventStream::loadEventData(uint64_t event_id) { - static char sql[ZM_SQL_MED_BUFSIZ]; - - snprintf(sql, sizeof(sql), - "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " - "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " + std::string sql = stringtf( + "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartDateTime` ) AS StartTimestamp, " + "unix_timestamp( `EndDateTime` ) AS EndTimestamp, " + "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS FramesDuration, " "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) { + exit(-1); } if ( !mysql_num_rows(result) ) { - Fatal("Unable to load event %d, not found in DB", event_id); + Fatal("Unable to load event %" PRIu64 ", not found in DB", event_id); } MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -150,9 +137,11 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0; event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]); event_data->start_time = atoi(dbrow[3]); - event_data->duration = dbrow[4] ? atof(dbrow[4]) : 0.0; - strncpy(event_data->video_file, dbrow[5], sizeof(event_data->video_file)-1); - std::string scheme_str = std::string(dbrow[6]); + event_data->end_time = dbrow[4] ? atoi(dbrow[4]) : 0; + event_data->duration = event_data->end_time - event_data->start_time; + event_data->frames_duration = dbrow[5] ? atof(dbrow[5]) : 0.0; + strncpy(event_data->video_file, dbrow[6], sizeof(event_data->video_file)-1); + std::string scheme_str = std::string(dbrow[7]); if ( scheme_str == "Deep" ) { event_data->scheme = Storage::DEEP; } else if ( scheme_str == "Medium" ) { @@ -160,14 +149,13 @@ bool EventStream::loadEventData(uint64_t event_id) { } else { event_data->scheme = Storage::SHALLOW; } - event_data->SaveJPEGs = dbrow[7] == nullptr ? 0 : atoi(dbrow[7]); - event_data->Orientation = (Monitor::Orientation)(dbrow[8] == nullptr ? 0 : atoi(dbrow[8])); + event_data->SaveJPEGs = dbrow[8] == nullptr ? 0 : atoi(dbrow[8]); + event_data->Orientation = (Monitor::Orientation)(dbrow[9] == nullptr ? 0 : atoi(dbrow[9])); mysql_free_result(result); if ( !monitor ) { monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); } else if ( monitor->Id() != event_data->monitor_id ) { - delete monitor; monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); } if ( !monitor ) { @@ -183,60 +171,55 @@ bool EventStream::loadEventData(uint64_t event_id) { const char *storage_path = storage->Path(); if ( event_data->scheme == Storage::DEEP ) { - struct tm *event_time = localtime(&event_data->start_time); + tm event_time = {}; + localtime_r(&event_data->start_time, &event_time); if ( storage_path[0] == '/' ) snprintf(event_data->path, sizeof(event_data->path), - "%s/%d/%02d/%02d/%02d/%02d/%02d/%02d", + "%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, - event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, - event_time->tm_hour, event_time->tm_min, event_time->tm_sec); + event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); else snprintf(event_data->path, sizeof(event_data->path), - "%s/%s/%d/%02d/%02d/%02d/%02d/%02d/%02d", + "%s/%s/%u/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, - event_time->tm_hour, event_time->tm_min, event_time->tm_sec); + event_time.tm_year-100, event_time.tm_mon+1, event_time.tm_mday, + event_time.tm_hour, event_time.tm_min, event_time.tm_sec); } else if ( event_data->scheme == Storage::MEDIUM ) { - struct tm *event_time = localtime(&event_data->start_time); + tm event_time = {}; + localtime_r(&event_data->start_time, &event_time); if ( storage_path[0] == '/' ) snprintf(event_data->path, sizeof(event_data->path), - "%s/%d/%04d-%02d-%02d/%" PRIu64, + "%s/%u/%04d-%02d-%02d/%" PRIu64, storage_path, event_data->monitor_id, - event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday, + event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, event_data->event_id); else snprintf(event_data->path, sizeof(event_data->path), - "%s/%s/%d/%04d-%02d-%02d/%" PRIu64, + "%s/%s/%u/%04d-%02d-%02d/%" PRIu64, staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_time->tm_year+1900, event_time->tm_mon+1, event_time->tm_mday, + event_time.tm_year+1900, event_time.tm_mon+1, event_time.tm_mday, event_data->event_id); } else { if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), "%s/%d/%" PRIu64, + snprintf(event_data->path, sizeof(event_data->path), "%s/%u/%" PRIu64, storage_path, event_data->monitor_id, event_data->event_id); else - snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%d/%" PRIu64, + snprintf(event_data->path, sizeof(event_data->path), "%s/%s/%u/%" PRIu64, staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id); } - updateFrameRate((double)event_data->frame_count/event_data->duration); - Debug(3, "fps set by frame_count(%d)/duration(%f)", - event_data->frame_count, event_data->duration); + updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); - snprintf(sql, sizeof(sql), "SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " - "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + sql = stringtf("SELECT `FrameId`, unix_timestamp(`TimeStamp`), `Delta` " + "FROM `Frames` WHERE `EventId` = %" PRIu64 " ORDER BY `FrameId` ASC", event_id); - result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + result = zmDbFetch(sql.c_str()); + if (!result) { + exit(-1); } event_data->n_frames = mysql_num_rows(result); @@ -284,11 +267,13 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->frames[id-1].in_db ); } + // Incomplete events might not have any frame data + event_data->last_frame_id = last_id; + if ( mysql_errno(&dbconn) ) { Error("Can't fetch row: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } - mysql_free_result(result); if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { @@ -296,9 +281,10 @@ bool EventStream::loadEventData(uint64_t event_id) { snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); } std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); - //char filepath[PATH_MAX]; - //snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); Debug(1, "Loading video file from %s", filepath.c_str()); + if ( ffmpeg_input ) + delete ffmpeg_input; + ffmpeg_input = new FFmpeg_Input(); if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) { Warning("Unable to open ffmpeg_input %s", filepath.c_str()); @@ -307,14 +293,15 @@ bool EventStream::loadEventData(uint64_t event_id) { } } + // Not sure about this if ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else - curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp; + curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp; } - Debug(2, "Event:%" PRIu64 ", Frames:%ld, Duration: %.2f", - event_data->event_id, event_data->frame_count, event_data->duration); + Debug(2, "Event:%" PRIu64 ", Frames:%ld, Last Frame ID(%ld, Duration: %.2f Frames Duration: %.2f", + event_data->event_id, event_data->frame_count, event_data->last_frame_id, event_data->duration, event_data->frames_duration); return true; } // bool EventStream::loadEventData( int event_id ) @@ -341,14 +328,16 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( (mode == MODE_SINGLE || mode == MODE_NONE) && - ((unsigned int)curr_frame_id == event_data->frame_count) + ((unsigned int)curr_frame_id == event_data->last_frame_id) ) { Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame"); curr_frame_id = 1; } else { - Debug(1, "mode is %s, current frame is %d, frame count is %d", - (mode == MODE_SINGLE ? "single" : "not single"), - curr_frame_id, event_data->frame_count ); + Debug(1, "mode is %s, current frame is %ld, frame count is %ld, last frame id is %ld", + StreamMode_Strings[(int) mode].c_str(), + curr_frame_id, + event_data->frame_count, + event_data->last_frame_id); } replay_rate = ZM_RATE_BASE; @@ -359,6 +348,13 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = false; } replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; + if ( replay_rate > 50 * ZM_RATE_BASE ) { + Warning("requested replay rate (%d) is too high. We only support up to 50x", replay_rate); + replay_rate = 50 * ZM_RATE_BASE; + } else if ( replay_rate < -50*ZM_RATE_BASE ) { + Warning("requested replay rate (%d) is too low. We only support up to -50x", replay_rate); + replay_rate = -50 * ZM_RATE_BASE; + } break; case CMD_STOP : Debug(1, "Got STOP command"); @@ -394,9 +390,9 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = true; replay_rate = ZM_RATE_BASE; step = 1; - if ( (unsigned int)curr_frame_id < event_data->frame_count ) + if ( (unsigned int)curr_frame_id < event_data->last_frame_id ) curr_frame_id += 1; - Debug(1, "Got SLOWFWD command new frame id %d", curr_frame_id); + Debug(1, "Got SLOWFWD command new frame id %ld", curr_frame_id); break; case CMD_SLOWREV : paused = true; @@ -404,13 +400,16 @@ void EventStream::processCommand(const CmdMsg *msg) { step = -1; curr_frame_id -= 1; if ( curr_frame_id < 1 ) curr_frame_id = 1; - Debug(1, "Got SLOWREV command new frame id %d", curr_frame_id); + Debug(1, "Got SLOWREV command new frame id %ld", curr_frame_id); break; case CMD_FASTREV : Debug(1, "Got FAST REV command"); paused = false; // Set play rate switch ( replay_rate ) { + case -1 * ZM_RATE_BASE : + replay_rate = -2 * ZM_RATE_BASE; + break; case -2 * ZM_RATE_BASE : replay_rate = -5 * ZM_RATE_BASE; break; @@ -425,7 +424,7 @@ void EventStream::processCommand(const CmdMsg *msg) { replay_rate = -50 * ZM_RATE_BASE; break; default : - replay_rate = -2 * ZM_RATE_BASE; + replay_rate = -1 * ZM_RATE_BASE; break; } break; @@ -489,14 +488,14 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( replay_rate >= 0 ) curr_frame_id = 0; else - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; paused = false; forceEventChange = true; break; case CMD_NEXT : Debug(1, "Got NEXT command"); if ( replay_rate >= 0 ) - curr_frame_id = event_data->frame_count+1; + curr_frame_id = event_data->last_frame_id+1; else curr_frame_id = 0; paused = false; @@ -505,9 +504,33 @@ void EventStream::processCommand(const CmdMsg *msg) { case CMD_SEEK : { // offset is in seconds - int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); - Debug(1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id); + + int int_part = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + int dec_part = ((unsigned char)msg->msg_data[5]<<24)|((unsigned char)msg->msg_data[6]<<16)|((unsigned char)msg->msg_data[7]<<8)|(unsigned char)msg->msg_data[8]; + + double offset = (double)int_part + (double)(dec_part / (double)1000000); + if ( offset < 0.0 ) { + Warning("Invalid offset, not seeking"); + break; + } + // This should get us close, but not all frames will have the same duration + curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration)+1; + if ( event_data->frames[curr_frame_id-1].offset > offset ) { + while ( (curr_frame_id --) && ( event_data->frames[curr_frame_id-1].offset > offset ) ) { + } + } else if ( event_data->frames[curr_frame_id-1].offset < offset ) { + while ( (curr_frame_id ++) && ( event_data->frames[curr_frame_id-1].offset > offset ) ) { + } + } + if ( curr_frame_id < 1 ) { + curr_frame_id = 1; + } else if ( (unsigned long)curr_frame_id > event_data->last_frame_id ) { + curr_frame_id = event_data->last_frame_id; + } + + curr_stream_time = event_data->frames[curr_frame_id-1].timestamp; + Debug(1, "Got SEEK command, to %f (new current frame id: %ld offset %f)", + offset, curr_frame_id, event_data->frames[curr_frame_id-1].offset); send_frame = true; break; } @@ -524,19 +547,22 @@ void EventStream::processCommand(const CmdMsg *msg) { struct { uint64_t event_id; - int progress; + double duration; + double progress; int rate; int zoom; bool paused; } status_data; status_data.event_id = event_data->event_id; - status_data.progress = (int)event_data->frames[curr_frame_id-1].offset; + status_data.duration = event_data->duration; + status_data.progress = event_data->frames[curr_frame_id-1].offset; status_data.rate = replay_rate; status_data.zoom = zoom; status_data.paused = paused; - Debug(2, "Event:%" PRIu64 ", Paused:%d, progress:%d Rate:%d, Zoom:%d", + Debug(2, "Event:%" PRIu64 ", Duration %f, Paused:%d, progress:%f Rate:%d, Zoom:%d", status_data.event_id, + status_data.duration, status_data.paused, status_data.progress, status_data.rate, @@ -546,7 +572,7 @@ void EventStream::processCommand(const CmdMsg *msg) { DataMsg status_msg; status_msg.msg_type = MSG_DATA_EVENT; memcpy(&status_msg.msg_data, &status_data, sizeof(status_data)); - Debug(1, "Size of msg %d", sizeof(status_data)); + Debug(1, "Size of msg %zu", sizeof(status_data)); if ( sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr)) < 0 ) { //if ( errno != EAGAIN ) { @@ -558,38 +584,45 @@ void EventStream::processCommand(const CmdMsg *msg) { if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) exit(0); - updateFrameRate((double)event_data->frame_count/event_data->duration); + updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); } // void EventStream::processCommand(const CmdMsg *msg) bool EventStream::checkEventLoaded() { - static char sql[ZM_SQL_SML_BUFSIZ]; + std::string sql; if ( curr_frame_id <= 0 ) { - snprintf(sql, sizeof(sql), + sql = stringtf( "SELECT `Id` FROM `Events` WHERE `MonitorId` = %d AND `Id` < %" PRIu64 " ORDER BY `Id` DESC LIMIT 1", event_data->monitor_id, event_data->event_id); - } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { - snprintf(sql, sizeof(sql), + } else if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) { + if ( !event_data->end_time ) { + // We are viewing an in-process event, so just reload it. + loadEventData(event_data->event_id); + if ( (unsigned int)curr_frame_id > event_data->last_frame_id ) + curr_frame_id = event_data->last_frame_id; + return false; + } + sql = stringtf( "SELECT `Id` FROM `Events` WHERE `MonitorId` = %d AND `Id` > %" PRIu64 " ORDER BY `Id` ASC LIMIT 1", event_data->monitor_id, event_data->event_id); } else { // No event change required - Debug(3, "No event change required, as curr frame %d <=> event frames %d", + Debug(3, "No event change required, as curr frame %ld <=> event frames %lu", curr_frame_id, event_data->frame_count); return false; } // Event change required. if ( forceEventChange || ( (mode != MODE_SINGLE) && (mode != MODE_NONE) ) ) { - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + Debug(1, "Checking for next event %s", sql.c_str()); + + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) { + exit(-1); } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); + if ( mysql_num_rows(result) != 1 ) { + Debug(1, "No rows returned for %s", sql.c_str()); } MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -605,13 +638,13 @@ bool EventStream::checkEventLoaded() { loadEventData(event_id); if ( replay_rate < 0 ) // rewind - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; else curr_frame_id = 1; - Debug(2, "New frame id = %d", curr_frame_id); + Debug(2, "New frame id = %ld", curr_frame_id); return true; } else { - Debug(2, "No next event loaded using %s. Pausing", sql); + Debug(2, "No next event loaded using %s. Pausing", sql.c_str()); if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -622,11 +655,11 @@ bool EventStream::checkEventLoaded() { mysql_free_result(result); forceEventChange = false; } else { - Debug(2, "Pausing because mode is %d", mode); + Debug(2, "Pausing because mode is %s", StreamMode_Strings[mode].c_str()); if ( curr_frame_id <= 0 ) curr_frame_id = 1; else - curr_frame_id = event_data->frame_count; + curr_frame_id = event_data->last_frame_id; paused = true; } return false; @@ -636,13 +669,13 @@ Image * EventStream::getImage( ) { static char filepath[PATH_MAX]; snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - Debug(2, "EventStream::getImage path(%s) from %s frame(%d) ", filepath, event_data->path, curr_frame_id); + Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", filepath, event_data->path, curr_frame_id); Image *image = new Image(filepath); return image; } bool EventStream::sendFrame(int delta_us) { - Debug(2, "Sending frame %d", curr_frame_id); + Debug(2, "Sending frame %ld", curr_frame_id); static char filepath[PATH_MAX]; static struct stat filestat; @@ -683,9 +716,6 @@ bool EventStream::sendFrame(int delta_us) { #endif // HAVE_LIBAVCODEC { - static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; - int img_buffer_size = 0; - uint8_t *img_buffer = temp_img_buffer; bool send_raw = (type == STREAM_JPEG) && ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)) && filepath[0]; @@ -718,7 +748,7 @@ bool EventStream::sendFrame(int delta_us) { // when stored as an mp4, we just have the rotation as a flag in the headers // so we need to rotate it before outputting if ( - (monitor->GetOptVideoWriter() == Monitor::H264PASSTHROUGH) + (monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH) and (event_data->Orientation != Monitor::ROTATE_0) ) { @@ -746,8 +776,11 @@ bool EventStream::sendFrame(int delta_us) { Error("Unable to get a frame"); return false; } - + Image *send_image = prepareImage(image); + static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; + int img_buffer_size = 0; + uint8_t *img_buffer = temp_img_buffer; switch ( type ) { case STREAM_JPEG : @@ -776,7 +809,7 @@ bool EventStream::sendFrame(int delta_us) { } // end if stream MPEG or other - fputs("\r\n\r\n", stdout); + fputs("\r\n", stdout); fflush(stdout); last_frame_sent = TV_2_FLOAT(now); return true; @@ -785,7 +818,7 @@ bool EventStream::sendFrame(int delta_us) { void EventStream::runStream() { openComms(); - checkInitialised(); + //checkInitialised(); if ( type == STREAM_JPEG ) fputs("Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n", stdout); @@ -795,13 +828,11 @@ void EventStream::runStream() { exit(0); } - Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration); - updateFrameRate((double)event_data->frame_count/event_data->duration); + updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); gettimeofday(&start, nullptr); uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; uint64_t last_frame_offset = 0; - bool in_event = true; double time_to_event = 0; while ( !zm_terminate ) { @@ -828,11 +859,11 @@ void EventStream::runStream() { if ( !paused ) { // Figure out if we should send this frame - Debug(3, "not paused at cur_frame_id (%d-1) mod frame_mod(%d)", curr_frame_id, frame_mod); + Debug(3, "not paused at cur_frame_id (%ld-1) mod frame_mod(%d)", curr_frame_id, frame_mod); // If we are streaming and this frame is due to be sent // frame mod defaults to 1 and if we are going faster than max_fps will get multiplied by 2 // so if it is 2, then we send every other frame, if is it 4 then every fourth frame, etc. - + if ( (frame_mod == 1) || (((curr_frame_id-1)%frame_mod) == 0) ) { send_frame = true; } @@ -843,8 +874,8 @@ void EventStream::runStream() { send_frame = true; } else if ( !send_frame ) { // We are paused, not stepping and doing nothing, meaning that comms didn't set send_frame to true - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( actual_delta_time > MAX_STREAM_DELAY ) { + double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; + if ( time_since_last_send > MAX_STREAM_DELAY ) { // Send keepalive Debug(2, "Sending keepalive frame"); send_frame = true; @@ -852,21 +883,19 @@ void EventStream::runStream() { } // end if streaming stepping or doing nothing // time_to_event > 0 means that we are not in the event - if ( time_to_event > 0 ) { - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - Debug(1, "Actual delta time = %f = %f - %f", actual_delta_time, TV_2_FLOAT(now), last_frame_sent); - // > 1 second - if ( actual_delta_time > 1 ) { - Debug(1, "Sending time to next event frame"); + if ( ( time_to_event > 0 ) and ( mode == MODE_ALL ) ) { + double time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; + Debug(1, "Time since last send = %f = %f - %f", time_since_last_send, TV_2_FLOAT(now), last_frame_sent); + if ( time_since_last_send > 1 /* second */ ) { static char frame_text[64]; - snprintf(frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event); + + snprintf(frame_text, sizeof(frame_text), "Time to %s event = %d seconds", + (replay_rate > 0 ? "next" : "previous" ), (int)time_to_event); if ( !sendTextFrame(frame_text) ) zm_terminate = true; - } else { - Debug(1, "Not Sending time to next event frame because actual delta time is %f", actual_delta_time); + send_frame = false; // In case keepalive was set } - //else - //{ + // FIXME ICON But we are not paused. We are somehow still in the event? double sleep_time = (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000)); //double sleep_time = (replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); @@ -903,7 +932,7 @@ void EventStream::runStream() { delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); Debug(3, "delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); // but must not exceed maxfps - delta_us = max(delta_us, 1000000/maxfps); + delta_us = std::max(delta_us, int(1000000/maxfps)); Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps); // +/- 1? What if we are skipping frames? @@ -916,7 +945,7 @@ void EventStream::runStream() { if ( (mode == MODE_SINGLE) && ( (curr_frame_id < 1 ) || - ((unsigned int)curr_frame_id >= event_data->frame_count) + ((unsigned int)curr_frame_id >= event_data->frame_count) ) ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); @@ -938,8 +967,8 @@ void EventStream::runStream() { // We assume that we are going forward and the next frame is in the future. delta_us = frame_data->offset * 1000000 - (now_usec-start_usec); // - (now_usec - start_usec); - Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %d offset %" PRId64 " - elapsed = %dusec", - now_usec, start_usec, now_usec-start_usec, frame_data->offset * 1000000, delta_us); + Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %" PRIu64 " offset %f - elapsed = %dusec", + now_usec, start_usec, static_cast(now_usec - start_usec), frame_data->offset * 1000000, delta_us); } else { Debug(2, "No last frame_offset, no sleep"); delta_us = 0; @@ -960,10 +989,10 @@ void EventStream::runStream() { delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); Debug(2, "Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)", - (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))), + delta_us, ZM_RATE_BASE, - (base_fps?base_fps:1), - (replay_rate?abs(replay_rate*2):0) + (base_fps ? base_fps : 1), + (replay_rate ? abs(replay_rate*2) : 0) ); if ( delta_us > 0 ) { if ( delta_us > MAX_SLEEP_USEC ) { @@ -976,29 +1005,28 @@ void EventStream::runStream() { //if ( step != 0 )// Adding 0 is cheaper than an if 0 // curr_frame_id starts at 1 though, so we might skip the first frame? curr_frame_id += step; - - // Detects when we hit end of event and will load the next event or previous event - if ( checkEventLoaded() ) { - // Have change of event - - // This next bit is to determine if we are in the current event time wise - // and whether to show an image saying how long until the next event. - if ( replay_rate > 0 ) { - // This doesn't make sense unless we have hit the end of the event. - time_to_event = event_data->frames[0].timestamp - curr_stream_time; - Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)", - replay_rate, time_to_event, - event_data->frames[0].timestamp, - curr_stream_time); - - } else if ( replay_rate < 0 ) { - time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; - Debug(1, "replay rate(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", - replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); - } // end if forward or reverse - - } // end if checkEventLoaded } // end if !paused + + // Detects when we hit end of event and will load the next event or previous event + if ( checkEventLoaded() ) { + // Have change of event + + // This next bit is to determine if we are in the current event time wise + // and whether to show an image saying how long until the next event. + if ( replay_rate > 0 ) { + // This doesn't make sense unless we have hit the end of the event. + time_to_event = event_data->frames[0].timestamp - curr_stream_time; + Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)", + replay_rate, time_to_event, + event_data->frames[0].timestamp, + curr_stream_time); + + } else if ( replay_rate < 0 ) { + time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; + Debug(1, "replay rate(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", + replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); + } // end if forward or reverse + } // end if checkEventLoaded } // end while ! zm_terminate #if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) @@ -1008,7 +1036,7 @@ void EventStream::runStream() { closeComms(); } // end void EventStream::runStream() -bool EventStream::send_file(const char * filepath) { +bool EventStream::send_file(const char *filepath) { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; int img_buffer_size = 0; @@ -1020,47 +1048,50 @@ bool EventStream::send_file(const char * filepath) { Error("Can't open %s: %s", filepath, strerror(errno)); return false; } - bool size_sent = false; - #if HAVE_SENDFILE static struct stat filestat; if ( fstat(fileno(fdj), &filestat) < 0 ) { + fclose(fdj); /* Close the file handle */ Error("Failed getting information about file %s: %s", filepath, strerror(errno)); return false; } + if ( !filestat.st_size ) { + fclose(fdj); /* Close the file handle */ + Info("File size is zero. Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); + return false; + } if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size) ) { fclose(fdj); /* Close the file handle */ - Info("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); + Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); return false; } - size_sent = true; - - if ( zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size ) { + int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); + if ( rc == (int)filestat.st_size ) { + // Success fclose(fdj); /* Close the file handle */ return true; } + Warning("Unable to send raw frame %ld: %s rc %d", curr_frame_id, strerror(errno), rc); #endif img_buffer_size = fread(img_buffer, 1, sizeof(temp_img_buffer), fdj); - if ( !size_sent ) { - if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size) ) { - fclose(fdj); /* Close the file handle */ - Info("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); - return false; - } - } - if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { - Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); + fclose(fdj); /* Close the file handle */ + if ( !img_buffer_size ) { + Info("Unable to read raw frame %ld: %s", curr_frame_id, strerror(errno)); return false; } - fclose(fdj); /* Close the file handle */ - return true; + return send_buffer(img_buffer, img_buffer_size); } // end bool EventStream::send_file(const char * filepath) bool EventStream::send_buffer(uint8_t* buffer, int size) { - fprintf(stdout, "Content-Length: %d\r\n\r\n", size); - if ( fwrite(buffer, size, 1, stdout) != 1 ) { - Error("Unable to send raw frame %u: %s", curr_frame_id, strerror(errno)); + if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { + Info("Unable to send raw frame %ld: %s", curr_frame_id, strerror(errno)); + return false; + } + int rc = fwrite(buffer, size, 1, stdout); + + if ( 1 != rc ) { + Error("Unable to send raw frame %ld: %s %d", curr_frame_id, strerror(errno), rc); return false; } return true; diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 023dd7a99..54b7687ab 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -20,12 +20,11 @@ #ifndef ZM_EVENTSTREAM_H #define ZM_EVENTSTREAM_H -#include "zm_image.h" -#include "zm_stream.h" -#include "zm_video.h" +#include "zm_define.h" #include "zm_ffmpeg_input.h" #include "zm_monitor.h" #include "zm_storage.h" +#include "zm_stream.h" #ifdef __cplusplus extern "C" { @@ -40,6 +39,7 @@ extern "C" { class EventStream : public StreamBase { public: typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; + static const std::string StreamMode_Strings[4]; protected: struct FrameData { @@ -54,11 +54,14 @@ class EventStream : public StreamBase { uint64_t event_id; unsigned int monitor_id; unsigned long storage_id; - unsigned long frame_count; + unsigned long frame_count; // Value of Frames column in Event + unsigned long last_frame_id; // Highest frame id known about. Can be < frame_count in incomplete events time_t start_time; + time_t end_time; double duration; + double frames_duration; char path[PATH_MAX]; - int n_frames; + int n_frames; // # of frame rows returned from database FrameData *frames; char video_file[PATH_MAX]; Storage::Schemes scheme; @@ -74,7 +77,7 @@ class EventStream : public StreamBase { StreamMode mode; bool forceEventChange; - int curr_frame_id; + long curr_frame_id; double curr_stream_time; bool send_frame; struct timeval start; // clock time when started the event @@ -82,13 +85,13 @@ class EventStream : public StreamBase { EventData *event_data; protected: - bool loadEventData( uint64_t event_id ); - bool loadInitialEventData( uint64_t init_event_id, unsigned int init_frame_id ); - bool loadInitialEventData( int monitor_id, time_t event_time ); + bool loadEventData(uint64_t event_id); + bool loadInitialEventData(uint64_t init_event_id, unsigned int init_frame_id); + bool loadInitialEventData(int monitor_id, time_t event_time); bool checkEventLoaded(); - void processCommand( const CmdMsg *msg ); - bool sendFrame( int delta_us ); + void processCommand(const CmdMsg *msg) override; + bool sendFrame(int delta_us); public: EventStream() : @@ -99,10 +102,7 @@ class EventStream : public StreamBase { send_frame(false), event_data(nullptr), storage(nullptr), - ffmpeg_input(nullptr), - // Used when loading frames from an mp4 - input_codec_context(nullptr), - input_codec(nullptr) + ffmpeg_input(nullptr) {} ~EventStream() { if ( event_data ) { @@ -113,10 +113,6 @@ class EventStream : public StreamBase { delete event_data; event_data = nullptr; } - if ( monitor ) { - delete monitor; - monitor = nullptr; - } if ( storage ) { delete storage; storage = nullptr; @@ -126,20 +122,16 @@ class EventStream : public StreamBase { ffmpeg_input = nullptr; } } - void setStreamStart( uint64_t init_event_id, unsigned int init_frame_id ); - void setStreamStart( int monitor_id, time_t event_time ); - void setStreamMode( StreamMode p_mode ) { - mode = p_mode; - } - void runStream(); + void setStreamStart(uint64_t init_event_id, unsigned int init_frame_id); + void setStreamStart(int monitor_id, time_t event_time); + void setStreamMode(StreamMode p_mode) { mode = p_mode; } + void runStream() override; Image *getImage(); private: - bool send_file( const char *file_path ); - bool send_buffer( uint8_t * buffer, int size ); + bool send_file(const char *filepath); + bool send_buffer(uint8_t * buffer, int size); Storage *storage; FFmpeg_Input *ffmpeg_input; - AVCodecContext *input_codec_context; - AVCodec *input_codec; }; #endif // ZM_EVENTSTREAM_H diff --git a/src/zm_exception.h b/src/zm_exception.h index a02653b88..1bceeed8a 100644 --- a/src/zm_exception.h +++ b/src/zm_exception.h @@ -22,8 +22,7 @@ #include -class Exception -{ +class Exception { protected: typedef enum { INFO, WARNING, ERROR, FATAL } Severity; @@ -32,33 +31,28 @@ protected: Severity mSeverity; public: - Exception( const std::string &message, Severity severity=ERROR ) : mMessage( message ), mSeverity( severity ) + explicit Exception(const std::string &message, const Severity severity=ERROR) : + mMessage(message), + mSeverity(severity) { } -public: - const std::string &getMessage() const - { - return( mMessage ); + const std::string &getMessage() const { + return mMessage; } - Severity getSeverity() const - { - return( mSeverity ); + Severity getSeverity() const { + return mSeverity; } - bool isInfo() const - { - return( mSeverity == INFO ); + bool isInfo() const { + return mSeverity == INFO; } - bool isWarning() const - { + bool isWarning() const { return( mSeverity == WARNING ); } - bool isError() const - { + bool isError() const { return( mSeverity == ERROR ); } - bool isFatal() const - { + bool isFatal() const { return( mSeverity == FATAL ); } }; diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 864b9e0a7..dcb22ed82 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -16,11 +16,12 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #include "zm_ffmpeg.h" -#include "zm_image.h" + +#include "zm_logger.h" #include "zm_rgb.h" + extern "C" { #include "libavutil/pixdesc.h" } @@ -30,44 +31,45 @@ extern "C" { void log_libav_callback(void *ptr, int level, const char *fmt, va_list vargs) { Logger *log = Logger::fetch(); int log_level = 0; - if ( level == AV_LOG_QUIET ) { // -8 + if (level == AV_LOG_QUIET) { // -8 log_level = Logger::NOLOG; - } else if ( level == AV_LOG_PANIC ) { //0 + } else if (level == AV_LOG_PANIC) { //0 log_level = Logger::PANIC; - } else if ( level == AV_LOG_FATAL ) { // 8 + } else if (level == AV_LOG_FATAL) { // 8 log_level = Logger::FATAL; - } else if ( level == AV_LOG_ERROR ) { // 16 + } else if (level == AV_LOG_ERROR) { // 16 log_level = Logger::WARNING; // ffmpeg outputs a lot of errors that don't really affect anything. - //log_level = Logger::ERROR; - } else if ( level == AV_LOG_WARNING ) { //24 + } else if (level == AV_LOG_WARNING) { //24 log_level = Logger::INFO; - //log_level = Logger::WARNING; - } else if ( level == AV_LOG_INFO ) { //32 + } else if (level == AV_LOG_INFO) { //32 log_level = Logger::DEBUG1; - //log_level = Logger::INFO; - } else if ( level == AV_LOG_VERBOSE ) { //40 + } else if (level == AV_LOG_VERBOSE) { //40 log_level = Logger::DEBUG2; - } else if ( level == AV_LOG_DEBUG ) { //48 + } else if (level == AV_LOG_DEBUG) { //48 log_level = Logger::DEBUG3; #ifdef AV_LOG_TRACE - } else if ( level == AV_LOG_TRACE ) { + } else if (level == AV_LOG_TRACE) { log_level = Logger::DEBUG8; #endif #ifdef AV_LOG_MAX_OFFSET - } else if ( level == AV_LOG_MAX_OFFSET ) { + } else if (level == AV_LOG_MAX_OFFSET) { log_level = Logger::DEBUG9; #endif } else { Error("Unknown log level %d", level); } - if ( log ) { + if (log) { char logString[8192]; - vsnprintf(logString, sizeof(logString)-1, fmt, vargs); - int length = strlen(logString); - // ffmpeg logs have a carriage return, so replace it with terminator - logString[length-1] = 0; - log->logPrint(false, __FILE__, __LINE__, log_level, logString); + int length = vsnprintf(logString, sizeof(logString)-1, fmt, vargs); + if (length > 0) { + if (static_cast(length) > sizeof(logString)-1) length = sizeof(logString)-1; + // ffmpeg logs have a carriage return, so replace it with terminator + logString[length-1] = 0; + log->logPrint(false, __FILE__, __LINE__, log_level, "%s", logString); + } else { + log->logPrint(false, __FILE__, __LINE__, AV_LOG_ERROR, "Can't encode log from av. fmt was %s", fmt); + } } } @@ -75,8 +77,8 @@ static bool bInit = false; void FFMPEGInit() { - if ( !bInit ) { - if ( logDebugging() && config.log_ffmpeg ) { + if (!bInit) { + if (logDebugging() && config.log_ffmpeg) { av_log_set_level(AV_LOG_DEBUG); av_log_set_callback(log_libav_callback); Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options"); @@ -84,7 +86,7 @@ void FFMPEGInit() { Debug(1,"Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor is not part of your debug targets"); av_log_set_level(AV_LOG_QUIET); } -#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) +#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 58, 9, 0) av_register_all(); #endif avformat_network_init(); @@ -103,9 +105,8 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp Debug(8,"Colours: %d SubpixelOrder: %d",p_colours,p_subpixelorder); - switch(p_colours) { + switch (p_colours) { case ZM_COLOUR_RGB24: - { if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { /* BGR subpixel order */ pf = AV_PIX_FMT_BGR24; @@ -114,16 +115,14 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp pf = AV_PIX_FMT_RGB24; } break; - } case ZM_COLOUR_RGB32: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { /* ARGB subpixel order */ pf = AV_PIX_FMT_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { /* ABGR subpixel order */ pf = AV_PIX_FMT_ABGR; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* BGRA subpixel order */ pf = AV_PIX_FMT_BGRA; } else { @@ -131,12 +130,11 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp pf = AV_PIX_FMT_RGBA; } break; - } case ZM_COLOUR_GRAY8: pf = AV_PIX_FMT_GRAY8; break; default: - Panic("Unexpected colours: %d",p_colours); + Panic("Unexpected colours: %d", p_colours); pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ break; } @@ -155,20 +153,21 @@ static int parse_key_value_pair(AVDictionary **pm, const char **buf, int ret; if (key && *key && strspn(*buf, key_val_sep)) { - (*buf)++; - val = av_get_token(buf, pairs_sep); + (*buf)++; + val = av_get_token(buf, pairs_sep); } if (key && *key && val && *val) - ret = av_dict_set(pm, key, val, flags); + ret = av_dict_set(pm, key, val, flags); else - ret = AVERROR(EINVAL); + ret = AVERROR(EINVAL); av_freep(&key); av_freep(&val); return ret; } + int av_dict_parse_string(AVDictionary **pm, const char *str, const char *key_val_sep, const char *pairs_sep, int flags) { @@ -221,61 +220,6 @@ simple_round: #endif #endif -int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { - AVFormatContext *s = avformat_alloc_context(); - int ret = 0; - - *avctx = nullptr; - if (!s) { - av_log(s, AV_LOG_ERROR, "Out of memory\n"); - ret = AVERROR(ENOMEM); - return ret; - } - - if (!oformat) { - if (format) { - oformat = av_guess_format(format, nullptr, nullptr); - if (!oformat) { - av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); - ret = AVERROR(EINVAL); - } - } else { - oformat = av_guess_format(nullptr, filename, nullptr); - if (!oformat) { - ret = AVERROR(EINVAL); - av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); - } - } - } - - if (ret) { - avformat_free_context(s); - return ret; - } - - s->oformat = oformat; -#if 0 - if (s->oformat->priv_data_size > 0) { - if (s->oformat->priv_class) { - // This looks wrong, we just allocated priv_data and now we are losing the pointer to it.FIXME - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } else { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if ( ! s->priv_data) { - av_log(s, AV_LOG_ERROR, "Out of memory\n"); - ret = AVERROR(ENOMEM); - return ret; - } - s->priv_data = nullptr; - } -#endif - - if (filename) strncpy(s->filename, filename, sizeof(s->filename)-1); - *avctx = s; - return 0; -} - static void zm_log_fps(double d, const char *postfix) { uint64_t v = lrintf(d * 100); if (!v) { @@ -290,7 +234,7 @@ static void zm_log_fps(double d, const char *postfix) { } #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -void zm_dump_codecpar ( const AVCodecParameters *par ) { +void zm_dump_codecpar(const AVCodecParameters *par) { Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)", par->codec_type, av_get_media_type_string(par->codec_type), @@ -307,9 +251,10 @@ void zm_dump_codecpar ( const AVCodecParameters *par ) { #endif void zm_dump_codec(const AVCodecContext *codec) { - Debug(1, "Dumping codec_context codec_type(%d) codec_id(%d %s) width(%d) height(%d) timebase(%d/%d) format(%s) " + Debug(1, "Dumping codec_context codec_type(%d %s) codec_id(%d %s) width(%d) height(%d) timebase(%d/%d) format(%s) " "gop_size %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d", codec->codec_type, + av_get_media_type_string(codec->codec_type), codec->codec_id, avcodec_get_name(codec->codec_id), codec->width, @@ -332,7 +277,7 @@ void zm_dump_codec(const AVCodecContext *codec) { /* "user interface" functions */ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) { - Debug(1, "Dumping stream index i(%d) index(%d)", i, index ); + Debug(1, "Dumping stream index i(%d) index(%d)", i, index); int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); AVStream *st = ic->streams[i]; AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", nullptr, 0); @@ -356,14 +301,17 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) ); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - Debug(1, "codec: %s", avcodec_get_name(st->codecpar->codec_id)); + Debug(1, "codec: %s %s", + avcodec_get_name(st->codecpar->codec_id), + av_get_media_type_string(st->codecpar->codec_type) + ); #else char buf[256]; avcodec_string(buf, sizeof(buf), st->codec, is_output); Debug(1, "codec: %s", buf); #endif - if ( st->sample_aspect_ratio.num && // default + if (st->sample_aspect_ratio.num && // default av_cmp_q(st->sample_aspect_ratio, codec->sample_aspect_ratio) ) { AVRational display_aspect_ratio; @@ -377,7 +325,7 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) display_aspect_ratio.num, display_aspect_ratio.den); } - if ( codec->codec_type == AVMEDIA_TYPE_VIDEO ) { + if (codec->codec_type == AVMEDIA_TYPE_VIDEO) { int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; int tbn = st->time_base.den && st->time_base.num; @@ -385,6 +333,9 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) zm_log_fps(av_q2d(st->avg_frame_rate), "fps"); if (tbn) zm_log_fps(1 / av_q2d(st->time_base), "stream tb numerator"); + } else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { + Debug(1, "profile %d channels %d sample_rate %d", + codec->profile, codec->channels, codec->sample_rate); } if (st->disposition & AV_DISPOSITION_DEFAULT) @@ -425,9 +376,26 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { return 0; } +enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat fmt) { + // Fix deprecated formats + switch ( fmt ) { + case AV_PIX_FMT_YUVJ422P : + return AV_PIX_FMT_YUV422P; + case AV_PIX_FMT_YUVJ444P : + return AV_PIX_FMT_YUV444P; + case AV_PIX_FMT_YUVJ440P : + return AV_PIX_FMT_YUV440P; + case AV_PIX_FMT_NONE : + case AV_PIX_FMT_YUVJ420P : + return AV_PIX_FMT_YUV420P; + default: + return fmt; + } +} + #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #else -unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { +unsigned int zm_av_packet_ref(AVPacket *dst, AVPacket *src) { av_new_packet(dst,src->size); memcpy(dst->data, src->data, src->size); dst->flags = src->flags; @@ -470,74 +438,73 @@ void av_packet_rescale_ts( bool is_video_stream(const AVStream * stream) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) #else #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( stream->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { + if (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) #else - if ( stream->codec->codec_type == CODEC_TYPE_VIDEO ) { + if (stream->codec->codec_type == CODEC_TYPE_VIDEO) #endif #endif + { return true; } #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); + Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); #endif return false; } -bool is_video_context(const AVCodecContext *codec_context ) { +bool is_video_context(const AVCodecContext *codec_context) { return #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - ( codec_context->codec_type == AVMEDIA_TYPE_VIDEO ); + (codec_context->codec_type == AVMEDIA_TYPE_VIDEO); #else - ( codec_context->codec_type == CODEC_TYPE_VIDEO ); + (codec_context->codec_type == CODEC_TYPE_VIDEO); #endif } -bool is_audio_stream(const AVStream * stream ) { +bool is_audio_stream(const AVStream * stream) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO ) { + if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) #else #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( stream->codec->codec_type == AVMEDIA_TYPE_AUDIO ) { + if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) #else - if ( stream->codec->codec_type == CODEC_TYPE_AUDIO ) { + if (stream->codec->codec_type == CODEC_TYPE_AUDIO) #endif #endif + { return true; } return false; } -bool is_audio_context(const AVCodecContext *codec_context ) { +bool is_audio_context(const AVCodecContext *codec_context) { return #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - ( codec_context->codec_type == AVMEDIA_TYPE_AUDIO ); + (codec_context->codec_type == AVMEDIA_TYPE_AUDIO); #else - ( codec_context->codec_type == CODEC_TYPE_AUDIO ); + (codec_context->codec_type == CODEC_TYPE_AUDIO); #endif } int zm_receive_packet(AVCodecContext *context, AVPacket &packet) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) int ret = avcodec_receive_packet(context, &packet); - if ( ret < 0 ) { - if ( AVERROR_EOF != ret ) { - Error("Error encoding (%d) (%s)", ret, - av_err2str(ret)); - } - return 0; + if ((ret < 0) and (AVERROR_EOF != ret)) { + Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); } - return 1; + return ret; // 1 or 0 #else int got_packet = 0; int ret = avcodec_encode_audio2(context, &packet, nullptr, &got_packet); - if ( ret < 0 ) { + if (ret < 0) { Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); + return ret; } - return got_packet; + return got_packet; // 1 #endif } // end int zm_receive_packet(AVCodecContext *context, AVPacket &packet) @@ -547,14 +514,14 @@ int zm_send_packet_receive_frame( AVPacket &packet) { int ret; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( (ret = avcodec_send_packet(context, &packet)) < 0 ) { + if ((ret = avcodec_send_packet(context, &packet)) < 0) { Error("Unable to send packet %s, continuing", av_make_error_string(ret).c_str()); return ret; } - if ( (ret = avcodec_receive_frame(context, frame)) < 0 ) { - if ( AVERROR(EAGAIN) == ret ) { + if ((ret = avcodec_receive_frame(context, frame)) < 0) { + if (AVERROR(EAGAIN) == ret) { // The codec may need more samples than it has, perfectly valid Debug(2, "Codec not ready to give us a frame"); } else { @@ -567,38 +534,39 @@ int zm_send_packet_receive_frame( return packet.size; # else int frameComplete = 0; - while ( !frameComplete ) { - if ( is_video_context(context) ) { - ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); - } else { - ret = avcodec_decode_audio4(context, frame, &frameComplete, &packet); - } - if ( ret < 0 ) { - Error("Unable to decode frame: %s", av_make_error_string(ret).c_str()); - return ret; - } - } // end while !frameComplete - return ret; + if (is_video_context(context)) { + ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); + Debug(2, "ret from decode_video %d, framecomplete %d", ret, frameComplete); + } else { + ret = avcodec_decode_audio4(context, frame, &frameComplete, &packet); + Debug(2, "ret from decode_audio %d, framecomplete %d", ret, frameComplete); + } + if (ret < 0) { + Error("Unable to decode frame: %s", av_make_error_string(ret).c_str()); + return ret; + } + return frameComplete ? ret : 0; #endif -} // end int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) +} // end int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet) /* Returns < 0 on error, 0 if codec not ready, 1 on success */ int zm_send_frame_receive_packet(AVCodecContext *ctx, AVFrame *frame, AVPacket &packet) { int ret; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( (ret = avcodec_send_frame(ctx, frame)) < 0 ) { + if (( (ret = avcodec_send_frame(ctx, frame)) < 0 ) and frame) { Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); return ret; } - if ( (ret = avcodec_receive_packet(ctx, &packet)) < 0 ) { - if ( AVERROR(EAGAIN) == ret ) { + if ((ret = avcodec_receive_packet(ctx, &packet)) < 0) { + if (AVERROR(EAGAIN) == ret) { // The codec may need more samples than it has, perfectly valid Debug(2, "Codec not ready to give us a packet"); return 0; - } else { + } else if (frame) { + // May get EOF if frame is NULL because it signals flushing Error("Could not recieve packet (error %d = '%s')", ret, av_make_error_string(ret).c_str()); } @@ -607,14 +575,14 @@ int zm_send_frame_receive_packet(AVCodecContext *ctx, AVFrame *frame, AVPacket & } #else int data_present; - if ( (ret = avcodec_encode_audio2( - ctx, &packet, frame, &data_present)) < 0 ) { + if ((ret = avcodec_encode_audio2( + ctx, &packet, frame, &data_present)) < 0) { Error("Could not encode frame (error '%s')", av_make_error_string(ret).c_str()); zm_av_packet_unref(&packet); return ret; } - if ( !data_present ) { + if (!data_present) { Debug(2, "Not ready to out a frame yet."); zm_av_packet_unref(&packet); return 0; @@ -623,58 +591,15 @@ int zm_send_frame_receive_packet(AVCodecContext *ctx, AVFrame *frame, AVPacket & return 1; } // end int zm_send_frame_receive_packet -void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) { - char b[10240]; - - double pts_time = (double)av_rescale_q(pkt->pts, - stream->time_base, - AV_TIME_BASE_Q - ) / AV_TIME_BASE; - - snprintf(b, sizeof(b), - " pts: %" PRId64 "=%f, dts: %" PRId64 - ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 - ", duration: %" +void zm_free_codec(AVCodecContext **ctx) { + if (*ctx) { + avcodec_close(*ctx); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - PRId64 -#else - "d" + // We allocate and copy in newer ffmpeg, so need to free it + avcodec_free_context(ctx); #endif - "\n", - pkt->pts, - pts_time, - pkt->dts, - pkt->size, - pkt->stream_index, - pkt->flags, - pkt->flags & AV_PKT_FLAG_KEY, - pkt->pos, - pkt->duration); - Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b); -} - -void dumpPacket(AVPacket *pkt, const char *text) { - char b[10240]; - - snprintf(b, sizeof(b), - " pts: %" PRId64 ", dts: %" PRId64 - ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 - ", duration: %" -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - PRId64 -#else - "d" -#endif - "\n", - pkt->pts, - pkt->dts, - pkt->size, - pkt->stream_index, - pkt->flags, - pkt->flags & AV_PKT_FLAG_KEY, - pkt->pos, - pkt->duration); - Debug(2, "%s:%d:%s: %s", __FILE__, __LINE__, text, b); + *ctx = NULL; + } // end if } void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb) { @@ -697,7 +622,7 @@ int zm_resample_audio( AVFrame *out_frame ) { #if defined(HAVE_LIBSWRESAMPLE) - if ( in_frame ) { + if (in_frame) { // Resample the in_frame into the audioSampleBuffer until we process the whole // decoded data. Note: pts does not survive resampling or converting Debug(2, "Converting %d to %d samples using swresample", @@ -706,34 +631,33 @@ int zm_resample_audio( Debug(2, "Sending NULL frame to flush resampler"); } int ret = swr_convert_frame(resample_ctx, out_frame, in_frame); - if ( ret < 0 ) { + if (ret < 0) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); return 0; } - Debug(3,"swr_get_delay %d", - swr_get_delay(resample_ctx, out_frame->sample_rate)); + Debug(3, "swr_get_delay %" PRIi64, swr_get_delay(resample_ctx, out_frame->sample_rate)); #else #if defined(HAVE_LIBAVRESAMPLE) - if ( ! in_frame ) { + if (!in_frame) { Error("Flushing resampler not supported by AVRESAMPLE"); return 0; } int ret = avresample_convert(resample_ctx, nullptr, 0, 0, in_frame->data, 0, in_frame->nb_samples); - if ( ret < 0 ) { + if (ret < 0) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); return 0; } int samples_available = avresample_available(resample_ctx); - if ( samples_available < out_frame->nb_samples ) { + if (samples_available < out_frame->nb_samples) { Debug(1, "Not enough samples yet (%d)", samples_available); return 0; } // Read a frame audio data from the resample fifo - if ( avresample_read(resample_ctx, out_frame->data, out_frame->nb_samples) != + if (avresample_read(resample_ctx, out_frame->data, out_frame->nb_samples) != out_frame->nb_samples) { Warning("Error reading resampled audio."); return 0; @@ -766,14 +690,14 @@ int zm_resample_get_delay( int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame) { int ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame->nb_samples); - if ( ret < 0 ) { + if (ret < 0) { Error("Could not reallocate FIFO to %d samples", av_audio_fifo_size(fifo) + frame->nb_samples); return 0; } /** Store the new samples in the FIFO buffer. */ ret = av_audio_fifo_write(fifo, (void **)frame->data, frame->nb_samples); - if ( ret < frame->nb_samples ) { + if (ret < frame->nb_samples) { Error("Could not write data to FIFO. %d written, expecting %d. Reason %s", ret, frame->nb_samples, av_make_error_string(ret).c_str()); return 0; @@ -783,13 +707,13 @@ int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame) { int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame) { // AAC requires 1024 samples per encode. Our input tends to be something else, so need to buffer them. - if ( frame->nb_samples > av_audio_fifo_size(fifo) ) { + if (frame->nb_samples > av_audio_fifo_size(fifo)) { Debug(1, "Not enough samples in fifo for AAC codec frame_size %d > fifo size %d", frame->nb_samples, av_audio_fifo_size(fifo)); return 0; } - if ( av_audio_fifo_read(fifo, (void **)frame->data, frame->nb_samples) < frame->nb_samples ) { + if (av_audio_fifo_read(fifo, (void **)frame->data, frame->nb_samples) < frame->nb_samples) { Error("Could not read data from FIFO"); return 0; } @@ -797,4 +721,3 @@ int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame) { zm_dump_frame(frame, "Out frame after fifo read"); return 1; } - diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index d09fee8b1..7431641c6 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -15,12 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ #ifndef ZM_FFMPEG_H #define ZM_FFMPEG_H -#include -#include "zm.h" + +#include "zm_config.h" +#include "zm_define.h" extern "C" { @@ -41,6 +42,9 @@ extern "C" { #include #include "libavutil/audio_fifo.h" #include "libavutil/imgutils.h" +#if HAVE_LIBAVUTIL_HWCONTEXT_H + #include "libavutil/hwcontext.h" +#endif /* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg * The original source is vlc (in modules/codec/avcodec/avcommon_compat.h) @@ -56,7 +60,7 @@ extern "C" { #else #include #endif - + #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #include #endif @@ -66,8 +70,8 @@ extern "C" { #include #include #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - -#if defined(HAVE_LIBAVUTIL_AVUTIL_H) + +#if defined(HAVE_LIBAVUTIL_AVUTIL_H) #if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) #define _AVPIXELFORMAT AVPixelFormat #else @@ -207,7 +211,7 @@ extern "C" { #endif #endif -/* A single function to initialize ffmpeg, to avoid multiple initializations */ +/* A single function to initialize ffmpeg, to avoid multiple initializations */ void FFMPEGInit(); void FFMPEGDeInit(); @@ -274,11 +278,6 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp #endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) -#ifndef avformat_alloc_output_context2 -int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename); -#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a) -#endif - #ifndef av_rescale_delta /** * Rescale a timestamp while preserving known durations. @@ -309,28 +308,27 @@ void zm_dump_codec(const AVCodecContext *codec); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar(const AVCodecParameters *par); #endif -#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" \ - " duration %" PRId64 \ - " layout %d pts %" PRId64, \ +#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d" \ + " layout %" PRIu64 " pts %" PRId64, \ text, \ frame->format, \ av_get_sample_fmt_name((AVSampleFormat)frame->format), \ frame->sample_rate, \ frame->nb_samples, \ - 0, 0, \ frame->channel_layout, \ frame->pts \ ); #if LIBAVUTIL_VERSION_CHECK(54, 4, 0, 74, 100) -#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \ +#define zm_dump_video_frame(frame, text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64 " keyframe: %d", \ text, \ frame->format, \ av_get_pix_fmt_name((AVPixelFormat)frame->format), \ frame->width, \ frame->height, \ frame->linesize[0], frame->linesize[1], \ - frame->pts \ + frame->pts, \ + frame->key_frame \ ); #else @@ -345,9 +343,67 @@ void zm_dump_codecpar(const AVCodecParameters *par); ); #endif +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +# define AV_PACKET_DURATION_FMT PRId64 +#else +# define AV_PACKET_DURATION_FMT "d" +#endif + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +#define CODEC_TYPE(stream) stream->codecpar->codec_type +#else +#define CODEC_TYPE(stream) stream->codec->codec_type +#endif +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +#define CODEC(stream) stream->codecpar +#else +#define CODEC(stream) stream->codec +#endif + + +#ifndef DBG_OFF +# define ZM_DUMP_PACKET(pkt, text) \ + Debug(2, "%s: pts: %" PRId64 ", dts: %" PRId64 \ + ", size: %d, stream_index: %d, flags: %04x, keyframe(%d) pos: %" PRId64 ", duration: %" AV_PACKET_DURATION_FMT, \ + text,\ + pkt.pts,\ + pkt.dts,\ + pkt.size,\ + pkt.stream_index,\ + pkt.flags,\ + pkt.flags & AV_PKT_FLAG_KEY,\ + pkt.pos,\ + pkt.duration) + +# define ZM_DUMP_STREAM_PACKET(stream, pkt, text) \ + if (logDebugging()) { \ + double pts_time = static_cast(av_rescale_q(pkt.pts, stream->time_base, AV_TIME_BASE_Q)) / AV_TIME_BASE; \ + \ + Debug(2, "%s: pts: %" PRId64 " * %u/%u=%f, dts: %" PRId64 \ + ", size: %d, stream_index: %d, %s flags: %04x, keyframe(%d) pos: %" PRId64", duration: %" AV_PACKET_DURATION_FMT, \ + text, \ + pkt.pts, \ + stream->time_base.num, \ + stream->time_base.den, \ + pts_time, \ + pkt.dts, \ + pkt.size, \ + pkt.stream_index, \ + av_get_media_type_string(CODEC_TYPE(stream)), \ + pkt.flags, \ + pkt.flags & AV_PKT_FLAG_KEY, \ + pkt.pos, \ + pkt.duration); \ + } + +#else +# define ZM_DUMP_PACKET(pkt, text) +# define ZM_DUMP_STREAM_PACKET(stream, pkt, text) +#endif + #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) - #define zm_av_packet_unref( packet ) av_packet_unref( packet ) - #define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) + #define zm_av_packet_unref(packet) av_packet_unref(packet) + #define zm_av_packet_ref(dst, src) av_packet_ref(dst, src) #else unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); #define zm_av_packet_unref( packet ) av_free_packet( packet ) @@ -355,12 +411,20 @@ void zm_dump_codecpar(const AVCodecParameters *par); void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb); #endif -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) +#if LIBAVCODEC_VERSION_CHECK(57, 24, 1, 45, 101) +#define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ + avcodec_send_packet(context, packet); \ + avcodec_receive_frame(context, rawFrame); #else - #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ + avcodec_decode_video2(context, rawFrame, frameComplete, packet) +#else + #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet) \ + avcodec_decode_video(context, rawFrame, frameComplete, packet->data, packet->size) #endif - +#endif + #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) #define zm_av_frame_alloc() av_frame_alloc() #else @@ -369,9 +433,10 @@ void zm_dump_codecpar(const AVCodecParameters *par); #if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) #define av_frame_free( input_avframe ) av_freep( input_avframe ) -#endif +#endif int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); +enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat ); bool is_video_stream(const AVStream *); bool is_audio_stream(const AVStream *); @@ -383,8 +448,6 @@ int zm_receive_packet(AVCodecContext *context, AVPacket &packet); int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *frame, AVPacket &packet); int zm_send_frame_receive_packet(AVCodecContext *context, AVFrame *frame, AVPacket &packet); -void dumpPacket(AVStream *, AVPacket *,const char *text=""); -void dumpPacket(AVPacket *,const char *text=""); void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb); #if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) @@ -415,5 +478,4 @@ int zm_resample_get_delay( int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame); int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame); - #endif // ZM_FFMPEG_H diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 6224c1f4f..b0c1401e7 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -17,28 +17,28 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" +#include "zm_ffmpeg_camera.h" + +#include "zm_ffmpeg_input.h" +#include "zm_monitor.h" +#include "zm_packet.h" #include "zm_signal.h" #include "zm_utils.h" #if HAVE_LIBAVFORMAT -#include "zm_ffmpeg_camera.h" - extern "C" { #include "libavutil/time.h" #if HAVE_LIBAVUTIL_HWCONTEXT_H #include "libavutil/hwcontext.h" #endif + #include "libavutil/pixdesc.h" } -#ifndef AV_ERROR_MAX_STRING_SIZE -#define AV_ERROR_MAX_STRING_SIZE 64 -#endif #include - +time_t start_read_time; #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) static enum AVPixelFormat hw_pix_fmt; @@ -94,8 +94,9 @@ static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) { #endif FfmpegCamera::FfmpegCamera( - int p_id, + const Monitor *monitor, const std::string &p_path, + const std::string &p_second_path, const std::string &p_method, const std::string &p_options, int p_width, @@ -110,7 +111,7 @@ FfmpegCamera::FfmpegCamera( const std::string &p_hwaccel_name, const std::string &p_hwaccel_device) : Camera( - p_id, + monitor, FFMPEG_SRC, p_width, p_height, @@ -124,33 +125,21 @@ FfmpegCamera::FfmpegCamera( p_record_audio ), mPath(p_path), + mSecondPath(p_second_path), mMethod(p_method), mOptions(p_options), hwaccel_name(p_hwaccel_name), hwaccel_device(p_hwaccel_device) { if ( capture ) { - Initialise(); + FFMPEGInit(); } - mFormatContext = nullptr; - mVideoStreamId = -1; - mAudioStreamId = -1; - mVideoCodecContext = nullptr; - mAudioCodecContext = nullptr; - mVideoCodec = nullptr; - mAudioCodec = nullptr; - mRawFrame = nullptr; - mFrame = nullptr; frameCount = 0; mCanCapture = false; - videoStore = nullptr; - have_video_keyframe = false; - packetqueue = nullptr; error_count = 0; use_hwaccel = true; #if HAVE_LIBAVUTIL_HWCONTEXT_H - hwFrame = nullptr; hw_device_ctx = nullptr; #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) hw_pix_fmt = AV_PIX_FMT_NONE; @@ -175,44 +164,16 @@ FfmpegCamera::FfmpegCamera( Panic("Unexpected colours: %d", colours); } - frame_buffer = nullptr; - // sws_scale needs 32bit aligned width and an extra 16 bytes padding, so recalculate imagesize, which was width*height*bytes_per_pixel -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - alignment = 32; - imagesize = av_image_get_buffer_size(imagePixFormat, width, height, alignment); - // av_image_get_linesize isn't aligned, so we have to do that. - linesize = FFALIGN(av_image_get_linesize(imagePixFormat, width, 0), alignment); -#else - alignment = 1; - linesize = FFALIGN(av_image_get_linesize(imagePixFormat, width, 0), alignment); - imagesize = avpicture_get_size(imagePixFormat, width, height); -#endif - if ( linesize != width * colours ) { - Debug(1, "linesize %d != width %d * colours %d = %d, allocating frame_buffer", linesize, width, colours, width*colours); - frame_buffer = (uint8_t *)av_malloc(imagesize); - } - - Debug(1, "ffmpegcamera: width %d height %d linesize %d colours %d image linesize %d imagesize %d", - width, height, linesize, colours, width*colours, imagesize); } // FfmpegCamera::FfmpegCamera FfmpegCamera::~FfmpegCamera() { Close(); - if ( capture ) { - Terminate(); - } FFMPEGDeInit(); } -void FfmpegCamera::Initialise() { - FFMPEGInit(); -} - -void FfmpegCamera::Terminate() { -} - int FfmpegCamera::PrimeCapture() { + start_read_time = time(nullptr); if ( mCanCapture ) { Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Close(); @@ -225,118 +186,81 @@ int FfmpegCamera::PrimeCapture() { } int FfmpegCamera::PreCapture() { - // If Reopen was called, then ffmpeg is closed and we need to reopen it. - if ( !mCanCapture ) - return OpenFfmpeg(); - // Nothing to do here return 0; } -int FfmpegCamera::Capture(Image &image) { - if ( !mCanCapture ) { +int FfmpegCamera::Capture(ZMPacket &zm_packet) { + if (!mCanCapture) return -1; + + start_read_time = time(nullptr); + int ret; + AVFormatContext *formatContextPtr; + + if ( mSecondFormatContext and + ( + av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q) + < + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ) ) { + // if audio stream is behind video stream, then read from audio, otherwise video + formatContextPtr = mSecondFormatContext; + Debug(4, "Using audio input because audio PTS %" PRId64 " < video PTS %" PRId64, + av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q), + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ); + } else { + formatContextPtr = mFormatContext; + Debug(4, "Using video input because %" PRId64 " >= %" PRId64, + (mAudioStream?av_rescale_q(mLastAudioPTS, mAudioStream->time_base, AV_TIME_BASE_Q):0), + av_rescale_q(mLastVideoPTS, mVideoStream->time_base, AV_TIME_BASE_Q) + ); + } + + if ((ret = av_read_frame(formatContextPtr, &packet)) < 0) { + if ( + // Check if EOF. + (ret == AVERROR_EOF || (formatContextPtr->pb && formatContextPtr->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info("Unable to read packet from stream %d: error %d \"%s\".", + packet.stream_index, ret, av_make_error_string(ret).c_str()); + } else { + Error("Unable to read packet from stream %d: error %d \"%s\".", + packet.stream_index, ret, av_make_error_string(ret).c_str()); + } return -1; } - int ret; - // If the reopen thread has a value, but mCanCapture != 0, then we have just - // reopened the connection to the device, and we can clean up the thread. + AVStream *stream = formatContextPtr->streams[packet.stream_index]; + ZM_DUMP_STREAM_PACKET(stream, packet, "ffmpeg_camera in"); - int frameComplete = false; - while ( !frameComplete && !zm_terminate ) { - ret = av_read_frame(mFormatContext, &packet); - if ( ret < 0 ) { - if ( - // Check if EOF. - ( - ret == AVERROR_EOF - || - (mFormatContext->pb && mFormatContext->pb->eof_reached) - ) || - // Check for Connection failure. - (ret == -110) - ) { - Info("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } else { - Error("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } - return -1; - } - bytes += packet.size; - - int keyframe = packet.flags & AV_PKT_FLAG_KEY; - if ( keyframe ) - have_video_keyframe = true; - - Debug(5, "Got packet from stream %d dts (%d) pts(%d)", - packet.stream_index, packet.pts, packet.dts); - // What about audio stream? Maybe someday we could do sound detection... - if ( - (packet.stream_index == mVideoStreamId) - && - (keyframe || have_video_keyframe) - ) { - ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet); - if ( ret < 0 ) { - if ( AVERROR(EAGAIN) != ret ) { - Warning("Unable to receive frame %d: code %d %s. error count is %d", - frameCount, ret, av_make_error_string(ret).c_str(), error_count); - error_count += 1; - if ( error_count > 100 ) { - Error("Error count over 100, going to close and re-open stream"); - return -1; - } - } - zm_av_packet_unref(&packet); - continue; - } - - frameComplete = 1; - zm_dump_video_frame(mRawFrame, "raw frame from decoder"); - -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( - (hw_pix_fmt != AV_PIX_FMT_NONE) - && - (mRawFrame->format == hw_pix_fmt) - ) { - /* retrieve data from GPU to CPU */ - ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0); - if ( ret < 0 ) { - Error("Unable to transfer frame at frame %d: %s, continuing", - frameCount, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - continue; - } - zm_dump_video_frame(hwFrame, "After hwtransfer"); - - hwFrame->pts = mRawFrame->pts; - input_frame = hwFrame; - } else { -#endif -#endif - input_frame = mRawFrame; -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - } -#endif +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + zm_packet.codec_type = stream->codecpar->codec_type; +#else + zm_packet.codec_type = stream->codec->codec_type; #endif + bytes += packet.size; + zm_packet.set_packet(&packet); + zm_packet.stream = stream; + zm_packet.pts = av_rescale_q(packet.pts, stream->time_base, AV_TIME_BASE_Q); + if ( packet.pts != AV_NOPTS_VALUE ) { + if ( stream == mVideoStream ) { + if (mFirstVideoPTS == AV_NOPTS_VALUE) + mFirstVideoPTS = packet.pts; - if ( transfer_to_image(image, mFrame, input_frame) < 0 ) { - zm_av_packet_unref(&packet); - return -1; - } - - frameCount++; + mLastVideoPTS = packet.pts - mFirstVideoPTS; } else { - Debug(4, "Different stream_index %d", packet.stream_index); - } // end if packet.stream_index == mVideoStreamId - zm_av_packet_unref(&packet); - } // end while ! frameComplete - return frameComplete ? 1 : 0; -} // FfmpegCamera::Capture + if (mFirstAudioPTS == AV_NOPTS_VALUE) + mFirstAudioPTS = packet.pts; + + mLastAudioPTS = packet.pts - mFirstAudioPTS; + } + } + zm_av_packet_unref(&packet); + + return 1; +} // FfmpegCamera::Capture int FfmpegCamera::PostCapture() { // Nothing to do here @@ -346,12 +270,11 @@ int FfmpegCamera::PostCapture() { int FfmpegCamera::OpenFfmpeg() { int ret; - have_video_keyframe = false; error_count = 0; // Open the input, not necessarily a file #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - if ( av_open_input_file(&mFormatContext, mPath.c_str(), nullptr, 0, nullptr) != 0 ) + if (av_open_input_file(&mFormatContext, mPath.c_str(), nullptr, 0, nullptr) != 0) #else // Handle options AVDictionary *opts = nullptr; @@ -362,7 +285,7 @@ int FfmpegCamera::OpenFfmpeg() { // Set transport method as specified by method field, rtpUni is default std::string protocol = mPath.substr(0, 4); - string_toupper(protocol); + StringToUpper(protocol); if ( protocol == "RTSP" ) { const std::string method = Method(); if ( method == "rtpMulti" ) { @@ -380,7 +303,6 @@ int FfmpegCamera::OpenFfmpeg() { Warning("Could not set rtsp_transport method '%s'", method.c_str()); } } // end if RTSP - // #av_dict_set(&opts, "timeout", "10000000", 0); // in microseconds. Debug(1, "Calling avformat_open_input for %s", mPath.c_str()); @@ -416,6 +338,7 @@ int FfmpegCamera::OpenFfmpeg() { } av_dict_free(&opts); + Debug(1, "Finding stream info"); #if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) ret = av_find_stream_info(mFormatContext); #else @@ -436,6 +359,7 @@ int FfmpegCamera::OpenFfmpeg() { if ( is_video_stream(stream) ) { if ( mVideoStreamId == -1 ) { mVideoStreamId = i; + mVideoStream = mFormatContext->streams[i]; // if we break, then we won't find the audio stream continue; } else { @@ -444,11 +368,13 @@ int FfmpegCamera::OpenFfmpeg() { } else if ( is_audio_stream(stream) ) { if ( mAudioStreamId == -1 ) { mAudioStreamId = i; + mAudioStream = mFormatContext->streams[i]; } else { Debug(2, "Have another audio stream."); } } } // end foreach stream + if ( mVideoStreamId == -1 ) { Error("Unable to locate video stream in %s", mPath.c_str()); return -1; @@ -456,24 +382,15 @@ int FfmpegCamera::OpenFfmpeg() { Debug(3, "Found video stream at index %d, audio stream at index %d", mVideoStreamId, mAudioStreamId); - packetqueue = new zm_packetqueue( - (mVideoStreamId > mAudioStreamId) ? mVideoStreamId : mAudioStreamId); + AVCodec *mVideoCodec = nullptr; + if ( mVideoStream-> #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // mVideoCodecContext = avcodec_alloc_context3(NULL); - // avcodec_parameters_to_context(mVideoCodecContext, - // mFormatContext->streams[mVideoStreamId]->codecpar); - // this isn't copied. - // mVideoCodecContext->time_base = - // mFormatContext->streams[mVideoStreamId]->codec->time_base; + codecpar #else + codec #endif - mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; -#ifdef CODEC_FLAG2_FAST - mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; -#endif - - if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { + ->codec_id == AV_CODEC_ID_H264 ) { if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == nullptr ) { Debug(1, "Failed to find decoder (h264_mmal)"); } else { @@ -482,7 +399,13 @@ int FfmpegCamera::OpenFfmpeg() { } if ( !mVideoCodec ) { - mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id); + mVideoCodec = avcodec_find_decoder(mVideoStream-> +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + codecpar +#else + codec +#endif + ->codec_id); if ( !mVideoCodec ) { // Try and get the codec from the codec context Error("Can't find codec for video stream from %s", mPath.c_str()); @@ -490,13 +413,24 @@ int FfmpegCamera::OpenFfmpeg() { } } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mVideoCodecContext = avcodec_alloc_context3(mVideoCodec); + avcodec_parameters_to_context(mVideoCodecContext, + mFormatContext->streams[mVideoStreamId]->codecpar); +#else + mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; +#endif +#ifdef CODEC_FLAG2_FAST + mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; +#endif + zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); if ( use_hwaccel && (hwaccel_name != "") ) { #if HAVE_LIBAVUTIL_HWCONTEXT_H // 3.2 doesn't seem to have all the bits in place, so let's require 3.3 and up -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) -// Print out available types + #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) + // Print out available types enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE ) Debug(1, "%s", av_hwdevice_get_type_name(type)); @@ -509,22 +443,24 @@ int FfmpegCamera::OpenFfmpeg() { Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type)); } -#if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) + #if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) // Get hw_pix_fmt for ( int i = 0;; i++ ) { const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); if ( !config ) { - Debug(1, "Decoder %s does not support device type %s.", - mVideoCodec->name, av_hwdevice_get_type_name(type)); + Debug(1, "Decoder %s does not support config %d.", + mVideoCodec->name, i); break; } if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && (config->device_type == type) ) { hw_pix_fmt = config->pix_fmt; - break; + Debug(1, "Decoder %s does support our type %s.", + mVideoCodec->name, av_hwdevice_get_type_name(type)); + //break; } else { - Debug(1, "decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.", + Debug(1, "Decoder %s hwConfig doesn't match our type: %s != %s, pix_fmt %s.", mVideoCodec->name, av_hwdevice_get_type_name(type), av_hwdevice_get_type_name(config->device_type), @@ -532,36 +468,41 @@ int FfmpegCamera::OpenFfmpeg() { ); } } // end foreach hwconfig -#else + #else hw_pix_fmt = find_fmt_by_hw_type(type); -#endif + #endif if ( hw_pix_fmt != AV_PIX_FMT_NONE ) { Debug(1, "Selected hw_pix_fmt %d %s", hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); + mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL; + //if (!lavc_param->check_hw_profile) + mVideoCodecContext->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH; + ret = av_hwdevice_ctx_create(&hw_device_ctx, type, - (hwaccel_device != "" ? hwaccel_device.c_str(): nullptr), nullptr, 0); + (hwaccel_device != "" ? hwaccel_device.c_str() : nullptr), nullptr, 0); + if ( ret < 0 and hwaccel_device != "" ) { + ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0); + } if ( ret < 0 ) { - Error("Failed to create hwaccel device. %s",av_make_error_string(ret).c_str()); + Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str()); hw_pix_fmt = AV_PIX_FMT_NONE; } else { Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); mVideoCodecContext->get_format = get_hw_format; mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); - hwFrame = zm_av_frame_alloc(); } } else { Debug(1, "Failed to find suitable hw_pix_fmt."); } -#else + #else Debug(1, "AVCodec not new enough for hwaccel"); -#endif + #endif #else Warning("HWAccel support not compiled in."); #endif } // end if hwaccel_name - // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) ret = avcodec_open(mVideoCodecContext, mVideoCodec); #else @@ -578,14 +519,25 @@ int FfmpegCamera::OpenFfmpeg() { } zm_dump_codec(mVideoCodecContext); - Debug(1, hwFrame ? "HWACCEL in use" : "HWACCEL not in use"); + if (mAudioStreamId == -1 and !monitor->GetSecondPath().empty()) { + Debug(1, "Trying secondary stream at %s", monitor->GetSecondPath().c_str()); + FFmpeg_Input *second_input = new FFmpeg_Input(); + if (second_input->Open(monitor->GetSecondPath().c_str()) > 0) { + mSecondFormatContext = second_input->get_format_context(); + mAudioStreamId = second_input->get_audio_stream_id(); + mAudioStream = second_input->get_audio_stream(); + } else { + Warning("Failed to open secondary input"); + } + } // end if have audio stream if ( mAudioStreamId >= 0 ) { + AVCodec *mAudioCodec = nullptr; if ( (mAudioCodec = avcodec_find_decoder( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - mFormatContext->streams[mAudioStreamId]->codecpar->codec_id + mAudioStream->codecpar->codec_id #else - mFormatContext->streams[mAudioStreamId]->codec->codec_id + mAudioStream->codec->codec_id #endif )) == nullptr ) { Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); @@ -594,60 +546,25 @@ int FfmpegCamera::OpenFfmpeg() { mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); avcodec_parameters_to_context( mAudioCodecContext, - mFormatContext->streams[mAudioStreamId]->codecpar + mAudioStream->codecpar ); #else - mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; - // = avcodec_alloc_context3(mAudioCodec); + mAudioCodecContext = mAudioStream->codec; #endif - zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); + zm_dump_stream_format((mSecondFormatContext?mSecondFormatContext:mFormatContext), mAudioStreamId, 0, 0); // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 ) { + if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 ) #else - if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 ) { + if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 ) #endif + { Error("Unable to open codec for audio stream from %s", mPath.c_str()); return -1; - } - zm_dump_codec(mAudioCodecContext); - } // end if find decoder - } // end if have audio_context - - // Allocate space for the native video frame - mRawFrame = zm_av_frame_alloc(); - - // Allocate space for the converted video frame - mFrame = zm_av_frame_alloc(); - - if ( mRawFrame == nullptr || mFrame == nullptr ) { - Error("Unable to allocate frame for %s", mPath.c_str()); - return -1; - } - mFrame->width = width; - mFrame->height = height; - - -#if HAVE_LIBSWSCALE - if ( !sws_isSupportedInput(mVideoCodecContext->pix_fmt) ) { - Error("swscale does not support the codec format for input: %s", - av_get_pix_fmt_name(mVideoCodecContext->pix_fmt) - ); - return -1; - } - - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format: %s", - av_get_pix_fmt_name(imagePixFormat) - ); - return -1; - } - -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale " - "option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE + } // end if opened + } // end if found decoder + } // end if mAudioStreamId if ( ((unsigned int)mVideoCodecContext->width != width) @@ -660,45 +577,16 @@ int FfmpegCamera::OpenFfmpeg() { mCanCapture = true; - return 0; -} // int FfmpegCamera::OpenFfmpeg() + return 1; +} // int FfmpegCamera::OpenFfmpeg() int FfmpegCamera::Close() { - Debug(2, "CloseFfmpeg called."); - mCanCapture = false; - if ( mFrame ) { - av_frame_free(&mFrame); - mFrame = nullptr; - } - if ( mRawFrame ) { - av_frame_free(&mRawFrame); - mRawFrame = nullptr; - } -#if HAVE_LIBAVUTIL_HWCONTEXT_H - if ( hwFrame ) { - av_frame_free(&hwFrame); - hwFrame = nullptr; - } -#endif - -#if HAVE_LIBSWSCALE - if ( mConvertContext ) { - sws_freeContext(mConvertContext); - mConvertContext = nullptr; - } -#endif - - if ( videoStore ) { - delete videoStore; - videoStore = nullptr; - } - if ( mVideoCodecContext ) { avcodec_close(mVideoCodecContext); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // avcodec_free_context(&mVideoCodecContext); + avcodec_free_context(&mVideoCodecContext); #endif mVideoCodecContext = nullptr; // Freed by av_close_input_file } @@ -725,480 +613,22 @@ int FfmpegCamera::Close() { mFormatContext = nullptr; } - if ( packetqueue ) { - delete packetqueue; - packetqueue = nullptr; - } - return 0; } // end FfmpegCamera::Close -// Function to handle capture and store -int FfmpegCamera::CaptureAndRecord( - Image &image, - timeval recording, - char* event_file - ) { - if ( !mCanCapture ) { - return -1; +int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { + if (zm_terminate) { + Debug(1, "Received terminate in cb"); + return zm_terminate; } - int ret; - - struct timeval video_buffer_duration = monitor->GetVideoBufferDuration(); - - int frameComplete = false; - while ( !frameComplete ) { - av_init_packet(&packet); - - ret = av_read_frame(mFormatContext, &packet); - if ( ret < 0 ) { - if ( - // Check if EOF. - ( - (ret == AVERROR_EOF) || - (mFormatContext->pb && mFormatContext->pb->eof_reached) - ) || - // Check for Connection failure. - (ret == -110) - ) { - Info("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } else { - Error("Unable to read packet from stream %d: error %d \"%s\".", - packet.stream_index, ret, av_make_error_string(ret).c_str()); - } - return -1; - } - - if ( (packet.pts != AV_NOPTS_VALUE) && (packet.pts < -100000) ) { - // Ignore packets that have crazy negative pts. - // They aren't supposed to happen. - Warning("Ignore packet because pts %" PRId64 " is massively negative." - " Error count is %d", packet.pts, error_count); - dumpPacket( - mFormatContext->streams[packet.stream_index], - &packet, - "Ignored packet"); - if ( error_count > 100 ) { - Error("Bad packet count over 100, going to close and re-open stream"); - return -1; - } - error_count += 1; - continue; - } - // If we get a good frame, decrease the error count.. We could zero it... - if ( error_count > 0 ) error_count -= 1; - - int keyframe = packet.flags & AV_PKT_FLAG_KEY; - bytes += packet.size; - dumpPacket( - mFormatContext->streams[packet.stream_index], - &packet, - "Captured Packet"); - if ( packet.dts == AV_NOPTS_VALUE ) { - packet.dts = packet.pts; - } - - // Video recording - if ( recording.tv_sec ) { - uint32_t last_event_id = monitor->GetLastEventId(); - uint32_t video_writer_event_id = monitor->GetVideoWriterEventId(); - - if ( last_event_id != video_writer_event_id ) { - Debug(2, "Have change of event. last_event(%d), our current (%d)", - last_event_id, video_writer_event_id); - - if ( videoStore ) { - Info("Re-starting video storage module"); - - // I don't know if this is important or not... but I figure we might - // as well write this last packet out to the store before closing it. - // Also don't know how much it matters for audio. - if ( packet.stream_index == mVideoStreamId ) { - // Write the packet to our video store - int ret = videoStore->writeVideoFramePacket(&packet); - if ( ret < 0 ) { // Less than zero and we skipped a frame - Warning("Error writing last packet to videostore."); - } - } // end if video - - delete videoStore; - videoStore = nullptr; - have_video_keyframe = false; - - monitor->SetVideoWriterEventId(0); - } // end if videoStore - } // end if end of recording - - if ( last_event_id && !videoStore ) { - // Instantiate the video storage module - - packetqueue->dumpQueue(); - if ( record_audio ) { - if ( mAudioStreamId == -1 ) { - Debug(3, "Record Audio on but no audio stream found"); - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - nullptr, - this->getMonitor()); - - } else { - Debug(3, "Video module initiated with audio stream"); - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - mFormatContext->streams[mAudioStreamId], - this->getMonitor()); - } - } else { - if ( mAudioStreamId >= 0 ) { - Debug(3, "Record_audio is false so exclude audio stream"); - } - videoStore = new VideoStore((const char *) event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - nullptr, - this->getMonitor()); - } // end if record_audio - - if ( !videoStore->open() ) { - delete videoStore; - videoStore = nullptr; - - } else { - monitor->SetVideoWriterEventId(last_event_id); - - // Need to write out all the frames from the last keyframe? - // No... need to write out all frames from when the event began. - // Due to PreEventFrames, this could be more than - // since the last keyframe. - unsigned int packet_count = 0; - ZMPacket *queued_packet; - struct timeval video_offset = {0}; - - // Clear all packets that predate the moment when the recording began - packetqueue->clear_unwanted_packets( - &recording, 0, mVideoStreamId); - - while ( (queued_packet = packetqueue->popPacket()) ) { - AVPacket *avp = queued_packet->av_packet(); - - // compute time offset between event start and first frame in video - if (packet_count == 0){ - monitor->SetVideoWriterStartTime(queued_packet->timestamp); - timersub(&queued_packet->timestamp, &recording, &video_offset); - Info("Event video offset is %.3f sec (<0 means video starts early)", - video_offset.tv_sec + video_offset.tv_usec*1e-6); - } - - packet_count += 1; - // Write the packet to our video store - Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", - avp->stream_index, - avp->flags & AV_PKT_FLAG_KEY, - packetqueue->size()); - if ( avp->stream_index == mVideoStreamId ) { - ret = videoStore->writeVideoFramePacket(avp); - have_video_keyframe = true; - } else if ( avp->stream_index == mAudioStreamId ) { - ret = videoStore->writeAudioFramePacket(avp); - } else { - Warning("Unknown stream id in queued packet (%d)", - avp->stream_index); - ret = -1; - } - if ( ret < 0 ) { - // Less than zero and we skipped a frame - } - delete queued_packet; - } // end while packets in the packetqueue - Debug(2, "Wrote %d queued packets", packet_count); - } - } // end if ! was recording - - } else { - // Not recording - - if ( videoStore ) { - Debug(1, "Deleting videoStore instance"); - delete videoStore; - videoStore = nullptr; - have_video_keyframe = false; - monitor->SetVideoWriterEventId(0); - } - } // end if recording or not - - // Buffer video packets, since we are not recording. - // All audio packets are keyframes, so only if it's a video keyframe - if ( packet.stream_index == mVideoStreamId ) { - if ( keyframe ) { - Debug(3, "Clearing queue"); - if (video_buffer_duration.tv_sec > 0 || video_buffer_duration.tv_usec > 0) { - packetqueue->clearQueue(&video_buffer_duration, mVideoStreamId); - } - else { - packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); - } - - if ( - packetqueue->packet_count(mVideoStreamId) - >= - monitor->GetImageBufferCount() - ) { - Warning( - "ImageBufferCount %d is too small. " - "Needs to be at least %d. " - "Either increase it or decrease time between keyframes", - monitor->GetImageBufferCount(), - packetqueue->packet_count(mVideoStreamId)+1); - } - - packetqueue->queuePacket(&packet); - } else if ( packetqueue->size() ) { - // it's a keyframe or we already have something in the queue - packetqueue->queuePacket(&packet); - } - } else if ( packet.stream_index == mAudioStreamId ) { - // Ensure that the queue always begins with a video keyframe - if ( record_audio && packetqueue->size() ) { - packetqueue->queuePacket(&packet); - } - } // end if packet type - - if ( packet.stream_index == mVideoStreamId ) { - if ( (have_video_keyframe || keyframe) && videoStore ) { - int ret = videoStore->writeVideoFramePacket(&packet); - if ( ret < 0 ) { - // Less than zero and we skipped a frame - Error("Unable to write video packet code: %d, framecount %d: %s", - ret, frameCount, av_make_error_string(ret).c_str()); - } else { - have_video_keyframe = true; - } - } // end if keyframe or have_video_keyframe - - ret = zm_send_packet_receive_frame(mVideoCodecContext, mRawFrame, packet); - if ( ret < 0 ) { - if ( AVERROR(EAGAIN) != ret ) { - Warning("Unable to receive frame %d: code %d %s. error count is %d", - frameCount, ret, av_make_error_string(ret).c_str(), error_count); - error_count += 1; - if ( error_count > 100 ) { - Error("Error count over 100, going to close and re-open stream"); - return -1; - } -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( (ret == AVERROR_INVALIDDATA ) && (hw_pix_fmt != AV_PIX_FMT_NONE) ) { - use_hwaccel = false; - return -1; - } -#endif -#endif - } - zm_av_packet_unref(&packet); - continue; - } - if ( error_count > 0 ) error_count--; - zm_dump_video_frame(mRawFrame, "raw frame from decoder"); -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - if ( - (hw_pix_fmt != AV_PIX_FMT_NONE) - && - (mRawFrame->format == hw_pix_fmt) - ) { - /* retrieve data from GPU to CPU */ - ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0); - if ( ret < 0 ) { - Error("Unable to transfer frame at frame %d: %s, continuing", - frameCount, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - continue; - } - zm_dump_video_frame(hwFrame, "After hwtransfer"); - - hwFrame->pts = mRawFrame->pts; - input_frame = hwFrame; - } else { -#endif -#endif - input_frame = mRawFrame; -#if HAVE_LIBAVUTIL_HWCONTEXT_H -#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) - } -#endif -#endif - if ( transfer_to_image(image, mFrame, input_frame) < 0 ) { - Error("Failed to transfer from frame to image"); - zm_av_packet_unref(&packet); - return -1; - } - - frameComplete = 1; - frameCount++; - } else if ( packet.stream_index == mAudioStreamId ) { - // FIXME best way to copy all other streams - frameComplete = 1; - if ( videoStore ) { - if ( record_audio ) { - if ( have_video_keyframe ) { - // Write the packet to our video store - // FIXME no relevance of last key frame - int ret = videoStore->writeAudioFramePacket(&packet); - if ( ret < 0 ) { - // Less than zero and we skipped a frame - Warning("Failure to write audio packet."); - zm_av_packet_unref(&packet); - return 0; - } - } else { - Debug(3, "Not recording audio because no video keyframe"); - } - } else { - Debug(4, "Not doing recording of audio packet"); - } - } else { - Debug(4, "Have audio packet, but not recording atm"); - } - zm_av_packet_unref(&packet); - return 0; - } else { -#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0) - Debug(3, "Some other stream index %d, %s", - packet.stream_index, - av_get_media_type_string( - mFormatContext->streams[packet.stream_index]->codecpar->codec_type) - ); -#else - Debug(3, "Some other stream index %d", packet.stream_index); -#endif - } // end if is video or audio or something else - - // the packet contents are ref counted... when queuing, we allocate another - // packet and reference it with that one, so we should always need to unref - // here, which should not affect the queued version. - zm_av_packet_unref(&packet); - } // end while ! frameComplete - return frameCount; -} // end FfmpegCamera::CaptureAndRecord - -int FfmpegCamera::transfer_to_image( - Image &image, - AVFrame *output_frame, - AVFrame *input_frame - ) { - uint8_t* image_buffer; // pointer to buffer in image - uint8_t* buffer; // pointer to either image_buffer or frame_buffer - - /* Request a writeable buffer of the target image */ - image_buffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( image_buffer == nullptr ) { - Error("Failed requesting writeable buffer for the captured image."); - return -1; - } - // if image_buffer was allocated then use it. - buffer = frame_buffer ? frame_buffer : image_buffer; - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. - int size = av_image_fill_arrays( - output_frame->data, output_frame->linesize, - buffer, imagePixFormat, width, height, - alignment - ); - if ( size < 0 ) { - Error("Problem setting up data pointers into image %s", - av_make_error_string(size).c_str()); - } else { - Debug(4, "av_image_fill_array %dx%d alignment: %d = %d, buffer size is %d", - width, height, alignment, size, image.Size()); - } - Debug(1, "ffmpegcamera: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, imagesize); - if ( linesize != (unsigned int)output_frame->linesize[0] ) { - Error("Bad linesize expected %d got %d", linesize, output_frame->linesize[0]); - } -#else - avpicture_fill((AVPicture *)output_frame, buffer, - imagePixFormat, width, height); -#endif - -#if HAVE_LIBSWSCALE - if ( !mConvertContext ) { - mConvertContext = sws_getContext( - input_frame->width, - input_frame->height, - (AVPixelFormat)input_frame->format, - width, height, - imagePixFormat, SWS_BICUBIC, nullptr, - nullptr, nullptr); - if ( mConvertContext == nullptr ) { - Error("Unable to create conversion context for %s from %s to %s", - mPath.c_str(), - av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - av_get_pix_fmt_name(imagePixFormat) - ); - return -1; - } - Debug(1, "Setup conversion context for %dx%d %s to %dx%d %s", - input_frame->width, input_frame->height, - av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - width, height, - av_get_pix_fmt_name(imagePixFormat) - ); - } - - int ret = - sws_scale( - mConvertContext, input_frame->data, input_frame->linesize, - 0, mVideoCodecContext->height, - output_frame->data, output_frame->linesize); - if ( ret < 0 ) { - Error("Unable to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s lines %d: code: %d", - input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height, - imagePixFormat, - av_get_pix_fmt_name(imagePixFormat), - output_frame->linesize[0], output_frame->linesize[1], - frameCount, - mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), - mVideoCodecContext->height, - ret - ); - return -1; - } - Debug(4, "Able to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s %dx%d ", - input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height, - imagePixFormat, - av_get_pix_fmt_name(imagePixFormat), - output_frame->linesize[0], output_frame->linesize[1], - frameCount, - mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), - output_frame->width, - output_frame->height - ); -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale " - "option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE - if ( buffer != image_buffer ) { - Debug(1, "Copying image-buffer to buffer"); - // Have to copy contents of image_buffer to directbuffer. - // Since linesize isn't the same have to copy line by line - uint8_t *image_buffer_ptr = image_buffer; - int row_size = output_frame->width * colours; - for ( int i = 0; i < output_frame->height; i++ ) { - memcpy(image_buffer_ptr, buffer, row_size); - image_buffer_ptr += row_size; - buffer += output_frame->linesize[0]; - } + time_t now = time(nullptr); + if (now - start_read_time > 10) { + Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10", + static_cast(now), + static_cast(start_read_time)); + return 1; } return 0; -} // end int FfmpegCamera::transfer_to_image - -int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { - // FfmpegCamera* camera = reinterpret_cast(ctx); - // Debug(4, "FfmpegInterruptCallback"); - return zm_terminate; } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index da815ab86..e6f55b634 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -22,11 +22,6 @@ #include "zm_camera.h" -#include "zm_buffer.h" -#include "zm_ffmpeg.h" -#include "zm_videostore.h" -#include "zm_packetqueue.h" - #if HAVE_LIBAVUTIL_HWCONTEXT_H typedef struct DecodeContext { AVBufferRef *hw_device_ref; @@ -39,29 +34,18 @@ typedef struct DecodeContext { class FfmpegCamera : public Camera { protected: std::string mPath; + std::string mSecondPath; std::string mMethod; std::string mOptions; + + std::string encoder_options; std::string hwaccel_name; std::string hwaccel_device; int frameCount; - - int alignment; /* ffmpeg wants line sizes to be 32bit aligned. Especially 4.3+ */ -#if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - int mAudioStreamId; - AVCodecContext *mVideoCodecContext; - AVCodecContext *mAudioCodecContext; - AVCodec *mVideoCodec; - AVCodec *mAudioCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; _AVPIXELFORMAT imagePixFormat; - AVFrame *input_frame; // Use to point to mRawFrame or hwFrame; - AVFrame *hwFrame; // Will also be used to indicate if hwaccel is in use bool use_hwaccel; //will default to on if hwaccel specified, will get turned off if there is a failure #if HAVE_LIBAVUTIL_HWCONTEXT_H AVBufferRef *hw_device_ctx = nullptr; @@ -73,25 +57,20 @@ class FfmpegCamera : public Camera { AVPacket packet; int OpenFfmpeg(); - int Close(); + int Close() override; bool mCanCapture; -#endif // HAVE_LIBAVFORMAT - - VideoStore *videoStore; - zm_packetqueue *packetqueue; - bool have_video_keyframe; #if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; #endif - uint8_t *frame_buffer; int error_count; public: FfmpegCamera( - int p_id, + const Monitor *monitor, const std::string &path, + const std::string &second_path, const std::string &p_method, const std::string &p_options, int p_width, @@ -112,16 +91,11 @@ class FfmpegCamera : public Camera { const std::string &Options() const { return mOptions; } const std::string &Method() const { return mMethod; } - void Initialise(); - void Terminate(); - - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); - int PostCapture(); + int PrimeCapture() override; + int PreCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; private: static int FfmpegInterruptCallback(void*ctx); - int transfer_to_image(Image &i, AVFrame *output_frame, AVFrame *input_frame); }; #endif // ZM_FFMPEG_CAMERA_H diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 0ae6e634b..7b78cc598 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -1,7 +1,7 @@ - #include "zm_ffmpeg_input.h" -#include "zm_logger.h" + #include "zm_ffmpeg.h" +#include "zm_logger.h" FFmpeg_Input::FFmpeg_Input() { input_format_context = nullptr; @@ -14,28 +14,34 @@ FFmpeg_Input::FFmpeg_Input() { } FFmpeg_Input::~FFmpeg_Input() { - if ( streams ) { - for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { - avcodec_close(streams[i].context); - streams[i].context = nullptr; - } - delete[] streams; - streams = nullptr; + if ( input_format_context ) { + Close(); } if ( frame ) { av_frame_free(&frame); frame = nullptr; } - if ( input_format_context ) { -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(input_format_context); -#else - avformat_close_input(&input_format_context); -#endif - input_format_context = nullptr; - } } // end ~FFmpeg_Input() +/* Takes streams provided from elsewhere. They might not come from the same source + * but we will treat them as if they are. */ +int FFmpeg_Input::Open( + const AVStream * video_in_stream, + const AVCodecContext * video_in_ctx, + const AVStream * audio_in_stream, + const AVCodecContext * audio_in_ctx + ) { + video_stream_id = video_in_stream->index; + int max_stream_index = video_in_stream->index; + + if ( audio_in_stream ) { + max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index; + audio_stream_id = audio_in_stream->index; + } + streams = new stream[max_stream_index+1]; + return 1; +} + int FFmpeg_Input::Open(const char *filepath) { int error; @@ -43,8 +49,8 @@ int FFmpeg_Input::Open(const char *filepath) { /** Open the input file to read from it. */ error = avformat_open_input(&input_format_context, filepath, nullptr, nullptr); if ( error < 0 ) { - Error("Could not open input file '%s' (error '%s')\n", - filepath, av_make_error_string(error).c_str() ); + Error("Could not open input file '%s' (error '%s')", + filepath, av_make_error_string(error).c_str()); input_format_context = nullptr; return error; } @@ -112,13 +118,37 @@ int FFmpeg_Input::Open(const char *filepath) { } // end foreach stream if ( video_stream_id == -1 ) - Error("Unable to locate video stream in %s", filepath); + Debug(1, "Unable to locate video stream in %s", filepath); if ( audio_stream_id == -1 ) Debug(3, "Unable to locate audio stream in %s", filepath); - return 0; + return 1; } // end int FFmpeg_Input::Open( const char * filepath ) +int FFmpeg_Input::Close( ) { + if ( streams ) { + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + avcodec_close(streams[i].context); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_free_context(&streams[i].context); + streams[i].context = nullptr; +#endif + } + delete[] streams; + streams = nullptr; + } + + if ( input_format_context ) { +#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) + av_close_input_file(input_format_context); +#else + avformat_close_input(&input_format_context); +#endif + input_format_context = nullptr; + } + return 1; +} // end int FFmpeg_Input::Close() + AVFrame *FFmpeg_Input::get_frame(int stream_id) { int frameComplete = false; AVPacket packet; @@ -140,42 +170,43 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) { packet.stream_index, ret, av_make_error_string(ret).c_str()); return nullptr; } - dumpPacket(input_format_context->streams[packet.stream_index], &packet, "Received packet"); + ZM_DUMP_STREAM_PACKET(input_format_context->streams[packet.stream_index], packet, "Received packet"); - if ( (stream_id < 0) || (packet.stream_index == stream_id) ) { - Debug(3, "Packet is for our stream (%d)", packet.stream_index); + if ( (stream_id >= 0) && (packet.stream_index != stream_id) ) { + Debug(1,"Packet is not for our stream (%d)", packet.stream_index ); + continue; + } - AVCodecContext *context = streams[packet.stream_index].context; + AVCodecContext *context = streams[packet.stream_index].context; - if ( frame ) { - av_frame_free(&frame); - frame = zm_av_frame_alloc(); + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } + ret = zm_send_packet_receive_frame(context, frame, packet); + if ( ret < 0 ) { + Error("Unable to decode frame at frame %d: %d %s, continuing", + streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str()); + zm_av_packet_unref(&packet); + av_frame_free(&frame); + continue; + } else { + if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) { + zm_dump_video_frame(frame, "resulting video frame"); } else { - frame = zm_av_frame_alloc(); - } - ret = zm_send_packet_receive_frame(context, frame, packet); - if ( ret < 0 ) { - Error("Unable to decode frame at frame %d: %d %s, continuing", - streams[packet.stream_index].frame_count, ret, av_make_error_string(ret).c_str()); - zm_av_packet_unref(&packet); - av_frame_free(&frame); - continue; - } else { - if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) { - zm_dump_video_frame(frame, "resulting video frame"); - } else { - zm_dump_frame(frame, "resulting frame"); - } + zm_dump_frame(frame, "resulting frame"); } + } - frameComplete = 1; - } // end if it's the right stream + frameComplete = 1; zm_av_packet_unref(&packet); - } // end while ! frameComplete + } // end while !frameComplete return frame; -} // end AVFrame *FFmpeg_Input::get_frame +} // end AVFrame *FFmpeg_Input::get_frame AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { Debug(1, "Getting frame from stream %d at %f", stream_id, at); @@ -187,47 +218,58 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { int ret; - if ( !frame ) { + if (!frame) { // Don't have a frame yet, so get a keyframe before the timestamp ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_FRAME); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to seek in stream"); return nullptr; } // Have to grab a frame to update our current frame to know where we are get_frame(stream_id); - } // end if ! frame - if ( !frame ) { - Warning("Unable to get frame."); - return nullptr; - } + if (!frame) { + Warning("Unable to get frame."); + return nullptr; + } + } // end if ! frame if ( (last_seek_request >= 0) && - (last_seek_request > seek_target ) + (last_seek_request > seek_target) && (frame->pts > seek_target) ) { zm_dump_frame(frame, "frame->pts > seek_target, seek backwards"); // our frame must be beyond our seek target. so go backwards to before it - if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, + if (( ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME - ) < 0 ) ) { - Error("Unable to seek in stream"); + ) ) < 0) { + Error("Unable to seek in stream %d", ret); return nullptr; } // Have to grab a frame to update our current frame to know where we are get_frame(stream_id); - zm_dump_frame(frame, "frame->pts > seek_target, got"); + if ( is_video_stream(input_format_context->streams[stream_id]) ) { + zm_dump_video_frame(frame, "frame->pts > seek_target, got"); + } else { + zm_dump_frame(frame, "frame->pts > seek_target, got"); + } + } else if ( last_seek_request == seek_target ) { + // paused case, sending keepalives + return frame; } // end if frame->pts > seek_target last_seek_request = seek_target; // Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want. if ( frame->pts <= seek_target ) { - zm_dump_frame(frame, "pts <= seek_target"); + if ( is_video_stream(input_format_context->streams[stream_id]) ) { + zm_dump_video_frame(frame, "pts <= seek_target"); + } else { + zm_dump_frame(frame, "pts <= seek_target"); + } while ( frame && (frame->pts < seek_target) ) { if ( !get_frame(stream_id) ) { Warning("Got no frame. returning nothing"); diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index 900f14d4a..5ca5f07dc 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -1,6 +1,8 @@ #ifndef ZM_FFMPEG_INPUT_H #define ZM_FFMPEG_INPUT_H +#include "zm_define.h" + #ifdef __cplusplus extern "C" { #endif @@ -19,16 +21,28 @@ class FFmpeg_Input { FFmpeg_Input(); ~FFmpeg_Input(); - int Open( const char *filename ); + int Open(const char *filename ); + int Open( + const AVStream *, + const AVCodecContext *, + const AVStream *, + const AVCodecContext *); int Close(); - AVFrame *get_frame( int stream_id=-1 ); - AVFrame *get_frame( int stream_id, double at ); - int get_video_stream_id() { + AVFrame *get_frame(int stream_id=-1); + AVFrame *get_frame(int stream_id, double at); + int get_video_stream_id() const { return video_stream_id; } - int get_audio_stream_id() { + int get_audio_stream_id() const { return audio_stream_id; } + AVStream *get_video_stream() { + return ( video_stream_id >= 0 ) ? input_format_context->streams[video_stream_id] : nullptr; + } + AVStream *get_audio_stream() { + return ( audio_stream_id >= 0 ) ? input_format_context->streams[audio_stream_id] : nullptr; + } + AVFormatContext *get_format_context() { return input_format_context; }; private: typedef struct { diff --git a/src/zm_ffmpeg_output.cpp b/src/zm_ffmpeg_output.cpp new file mode 100644 index 000000000..b031399b9 --- /dev/null +++ b/src/zm_ffmpeg_output.cpp @@ -0,0 +1,179 @@ + +#include "zm_ffmpeg_input.h" +#include "zm_logger.h" +#include "zm_ffmpeg.h" + +FFmpeg_Output::FFmpeg_Output() { + input_format_context = NULL; + video_stream_id = -1; + audio_stream_id = -1; + + FFMPEGInit(); +} +FFmpeg_Output::~FFmpeg_Output() { +} + +int FFmpeg_Output::Open( const char *filepath ) { + + int error; + + /** Open the input file to read from it. */ + if ( (error = avformat_open_input( &input_format_context, filepath, NULL, NULL)) < 0 ) { + + Error("Could not open input file '%s' (error '%s')\n", + filepath, av_make_error_string(error).c_str() ); + input_format_context = NULL; + return error; + } + + /** Get information on the input file (number of streams etc.). */ + if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) { + Error( "Could not open find stream info (error '%s')\n", + av_make_error_string(error).c_str() ); + avformat_close_input(&input_format_context); + return error; + } + + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + if ( is_video_stream( input_format_context->streams[i] ) ) { + zm_dump_stream_format(input_format_context, i, 0, 0); + if ( video_stream_id == -1 ) { + video_stream_id = i; + // if we break, then we won't find the audio stream + } else { + Warning( "Have another video stream." ); + } + } else if ( is_audio_stream( input_format_context->streams[i] ) ) { + if ( audio_stream_id == -1 ) { + audio_stream_id = i; + } else { + Warning( "Have another audio stream." ); + } + } + + streams[i].frame_count = 0; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + streams[i].context = avcodec_alloc_context3( NULL ); + avcodec_parameters_to_context( streams[i].context, input_format_context->streams[i]->codecpar ); +#else + streams[i].context = input_format_context->streams[i]->codec; +#endif + + if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) { + Error( "Could not find input codec\n"); + avformat_close_input(&input_format_context); + return AVERROR_EXIT; + } else { + Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i ); + } + + if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) { + Error( "Could not open input codec (error '%s')\n", + av_make_error_string(error).c_str() ); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_free_context( &streams[i].context ); +#endif + avformat_close_input(&input_format_context); + return error; + } + } // end foreach stream + + if ( video_stream_id == -1 ) + Error( "Unable to locate video stream in %s", filepath ); + if ( audio_stream_id == -1 ) + Debug( 3, "Unable to locate audio stream in %s", filepath ); + + return 0; +} // end int FFmpeg_Output::Open( const char * filepath ) + +AVFrame *FFmpeg_Output::get_frame( int stream_id ) { + Debug(1, "Getting frame from stream %d", stream_id ); + + int frameComplete = false; + AVPacket packet; + av_init_packet( &packet ); + AVFrame *frame = zm_av_frame_alloc(); + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + while ( !frameComplete ) { + int ret = av_read_frame( input_format_context, &packet ); + if ( ret < 0 ) { + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (input_format_context->pb && input_format_context->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned %s.", errbuf ); + return NULL; + } + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return NULL; + } + + if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) { + Debug(1,"Packet is for our stream (%d)", packet.stream_index ); + + AVCodecContext *context = streams[packet.stream_index].context; + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_send_packet( context, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } else { + Debug(1, "Success getting a packet"); + } + +#if HAVE_AVUTIL_HWCONTEXT_H + if ( hwaccel ) { + ret = avcodec_receive_frame( context, hwFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = av_hwframe_transfer_data(frame, hwFrame, 0); + if (ret < 0) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + } else { +#endif + Debug(1,"Getting a frame?"); + ret = avcodec_receive_frame( context, frame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + +#if HAVE_AVUTIL_HWCONTEXT_H + } +#endif + + frameComplete = 1; +# else + ret = zm_avcodec_decode_video( streams[packet.stream_index].context, frame, &frameComplete, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } +#endif + } // end if it's the right stream + + zm_av_packet_unref( &packet ); + + } // end while ! frameComplete + return frame; + +} // end AVFrame *FFmpeg_Output::get_frame diff --git a/src/zm_ffmpeg_output.h b/src/zm_ffmpeg_output.h new file mode 100644 index 000000000..76afad0d7 --- /dev/null +++ b/src/zm_ffmpeg_output.h @@ -0,0 +1,46 @@ +#ifndef ZM_FFMPEG_INPUT_H +#define ZM_FFMPEG_INPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" +#include "libavcodec/avcodec.h" + +#ifdef __cplusplus +} +#endif + +class FFmpeg_Output { + + public: + FFmpeg_Output(); + ~FFmpeg_Output(); + + int Open( const char *filename ); + int Close(); + AVFrame *put_frame( int stream_id=-1 ); + AVFrame *put_packet( int stream_id=-1 ); + int get_video_stream_id() { + return video_stream_id; + } + int get_audio_stream_id() { + return audio_stream_id; + } + + private: + typedef struct { + AVCodecContext *context; + AVCodec *codec; + int frame_count; + } stream; + + stream streams[2]; + int video_stream_id; + int audio_stream_id; + AVFormatContext *input_format_context; +}; + +#endif diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index 53025f597..731909166 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -17,117 +17,19 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // +#include "zm_fifo.h" + +#include "zm_monitor.h" +#include "zm_signal.h" #include #include -#include -#include -#include +#include +#include -#include "zm.h" -#include "zm_time.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_fifo.h" #define RAW_BUFFER 512 -static bool zm_fifodbg_inited = false; -FILE *zm_fifodbg_log_fd = nullptr; -char zm_fifodbg_log[PATH_MAX] = ""; +#define PIPE_SIZE 1024*1024 -static bool zmFifoDbgOpen() { - if ( zm_fifodbg_log_fd ) - fclose(zm_fifodbg_log_fd); - zm_fifodbg_log_fd = nullptr; - signal(SIGPIPE, SIG_IGN); - FifoStream::fifo_create_if_missing(zm_fifodbg_log); - int fd = open(zm_fifodbg_log, O_WRONLY|O_NONBLOCK|O_TRUNC); - if ( fd < 0 ) - return false; - int res = flock(fd, LOCK_EX | LOCK_NB); - if ( res < 0 ) { - close(fd); - return false; - } - zm_fifodbg_log_fd = fdopen(fd, "wb"); - if ( zm_fifodbg_log_fd == nullptr ) { - close(fd); - return false; - } - return true; -} - -int zmFifoDbgInit(Monitor *monitor) { - zm_fifodbg_inited = true; - snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/%d/dbgpipe.log", - monitor->getStorage()->Path(), monitor->Id()); - zmFifoDbgOpen(); - return 1; -} - -void zmFifoDbgOutput( - int hex, - const char * const file, - const int line, - const int level, - const char *fstring, - ... - ) { - char dbg_string[8192]; - int str_size = sizeof(dbg_string); - - va_list arg_ptr; - if ( (!zm_fifodbg_inited) || ( !zm_fifodbg_log_fd && !zmFifoDbgOpen() ) ) - return; - - char *dbg_ptr = dbg_string; - va_start(arg_ptr, fstring); - if ( hex ) { - unsigned char *data = va_arg(arg_ptr, unsigned char *); - int len = va_arg(arg_ptr, int); - dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), "%d:", len); - for ( int i = 0; i < len; i++ ) { - dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), " %02x", data[i]); - } - } else { - dbg_ptr += vsnprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), fstring, arg_ptr); - } - va_end(arg_ptr); - strncpy(dbg_ptr++, "\n", 2); - int res = fwrite(dbg_string, dbg_ptr-dbg_string, 1, zm_fifodbg_log_fd); - if ( res != 1 ) { - fclose(zm_fifodbg_log_fd); - zm_fifodbg_log_fd = nullptr; - } else { - fflush(zm_fifodbg_log_fd); - } -} - -bool FifoStream::sendRAWFrames() { - static unsigned char buffer[RAW_BUFFER]; - int fd = open(stream_path, O_RDONLY); - if ( fd < 0 ) { - Error("Can't open %s: %s", stream_path, strerror(errno)); - return false; - } - while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) { - if ( bytes_read == 0 ) - continue; - if ( bytes_read < 0 ) { - Error("Problem during reading: %s", strerror(errno)); - close(fd); - return false; - } - if ( fwrite(buffer, bytes_read, 1, stdout) != 1 ) { - Error("Problem during writing: %s", strerror(errno)); - close(fd); - return false; - } - fflush(stdout); - } - close(fd); - return true; -} - -void FifoStream::file_create_if_missing( +void Fifo::file_create_if_missing( const char * path, bool is_fifo, bool delete_fake_fifo @@ -140,124 +42,147 @@ void FifoStream::file_create_if_missing( unlink(path); } int fd; - if ( !is_fifo ) { + if (!is_fifo) { Debug(5, "Creating non fifo file as requested: %s", path); - fd = open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); - close(fd); + fd = ::open(path, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); + ::close(fd); return; } Debug(5, "Making fifo file of: %s", path); mkfifo(path, S_IRUSR|S_IWUSR); } -void FifoStream::fifo_create_if_missing( +void Fifo::fifo_create_if_missing( const char * path, bool delete_fake_fifo ) { file_create_if_missing(path, true, delete_fake_fifo); } -bool FifoStream::sendMJEGFrames() { - static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; - int fd = open(stream_path, O_RDONLY); - if ( fd < 0 ) { - Error("Can't open %s: %s", stream_path, strerror(errno)); - return false; - } - total_read = 0; - while ( - (bytes_read = read(fd, buffer+total_read, ZM_MAX_IMAGE_SIZE-total_read)) - ) { - if ( bytes_read < 0 ) { - Error("Problem during reading: %s", strerror(errno)); - close(fd); +Fifo::~Fifo() { + close(); +} +bool Fifo::open() { + fifo_create_if_missing(path.c_str()); + if (!on_blocking_abort) { + if ( (outfile = fopen(path.c_str(), "wb")) == nullptr ) { + Error("Can't open %s for writing: %s", path.c_str(), strerror(errno)); + return false; + } + } else { + raw_fd = ::open(path.c_str(), O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (raw_fd < 0) + return false; + outfile = fdopen(raw_fd, "wb"); + if (outfile == nullptr) { + ::close(raw_fd); + raw_fd = -1; return false; } - total_read += bytes_read; } - close(fd); - - if ( (total_read == 0) || (frame_count%frame_mod != 0) ) - return true; - - if ( fprintf(stdout, - "--ZoneMinderFrame\r\n" - "Content-Type: image/jpeg\r\n" - "Content-Length: %d\r\n\r\n", - total_read) < 0 ) { - Error("Problem during writing: %s", strerror(errno)); - return false; +#ifdef __linux__ + int ret = fcntl(raw_fd, F_SETPIPE_SZ, PIPE_SIZE); + if (ret < 0) { + Error("set pipe size failed."); } - - if ( fwrite(buffer, total_read, 1, stdout) != 1 ) { - Error("Problem during reading: %s", strerror(errno)); - return false; + long pipe_size = (long)fcntl(raw_fd, F_GETPIPE_SZ); + if (pipe_size == -1) { + Error("get pipe size failed."); } - fprintf(stdout, "\r\n\r\n"); - fflush(stdout); - last_frame_sent = TV_2_FLOAT(now); - frame_count++; + Debug(1, "default pipe size: %ld\n", pipe_size); +#endif return true; } -void FifoStream::setStreamStart(const char * path) { - stream_path = strdup(path); +bool Fifo::close() { + if (outfile) { + fclose(outfile); + } + + return true; } -void FifoStream::setStreamStart(int monitor_id, const char * format) { - char diag_path[PATH_MAX]; - const char * filename; - Monitor * monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); +bool Fifo::writePacket(const ZMPacket &packet) { + if (!(outfile or open())) return false; - if ( !strcmp(format, "reference") ) { - stream_type = MJPEG; - filename = "diagpipe-r.jpg"; - } else if ( !strcmp(format, "delta") ) { - filename = "diagpipe-d.jpg"; - stream_type = MJPEG; - } else { - stream_type = RAW; - filename = "dbgpipe.log"; - } - - snprintf(diag_path, sizeof(diag_path), "%s/%d/%s", - monitor->getStorage()->Path(), monitor->Id(), filename); - setStreamStart(diag_path); -} - -void FifoStream::runStream() { - if ( stream_type == MJPEG ) { - fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n"); - } else { - fprintf(stdout, "Content-Type: text/html\r\n\r\n"); - } - - char lock_file[PATH_MAX]; - snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path); - file_create_if_missing(lock_file, false); - - int fd_lock = open(lock_file, O_RDONLY); - if ( fd_lock < 0 ) { - Error("Can't open %s: %s", lock_file, strerror(errno)); - return; - } - int res = flock(fd_lock, LOCK_EX | LOCK_NB); - if ( res < 0 ) { - Error("Flocking problem on %s: - %s", lock_file, strerror(errno)); - close(fd_lock); - return; - } - - while ( !zm_terminate ) { - gettimeofday(&now, nullptr); - checkCommandQueue(); - if ( stream_type == MJPEG ) { - if ( !sendMJEGFrames() ) - zm_terminate = true; + Debug(2, "Writing header ZM %u %" PRId64, packet.packet.size, packet.pts); + // Going to write a brief header + if (fprintf(outfile, "ZM %u %" PRId64 "\n", packet.packet.size, packet.pts) < 0) { + if (errno != EAGAIN) { + Error("Problem during writing: %s", strerror(errno)); } else { - if ( !sendRAWFrames() ) - zm_terminate = true; + Debug(1, "Problem during writing: %s", strerror(errno)); } + return false; } - close(fd_lock); + + if (fwrite(packet.packet.data, packet.packet.size, 1, outfile) != 1) { + Debug(1, "Unable to write to '%s': %s", path.c_str(), strerror(errno)); + return false; + } + return true; +} + +bool Fifo::writePacket(std::string filename, const ZMPacket &packet) { + FILE *outfile = nullptr; + + int raw_fd = ::open(filename.c_str(), O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (raw_fd < 0) + return false; + outfile = fdopen(raw_fd, "wb"); + if (outfile == nullptr) { + ::close(raw_fd); + return false; + } + + Debug(4, "Writing packet of size %d pts %" PRId64, packet.packet.size, packet.pts); + if (fwrite(packet.packet.data, packet.packet.size, 1, outfile) != 1) { + Debug(1, "Unable to write to '%s': %s", filename.c_str(), strerror(errno)); + fclose(outfile); + return false; + } + + fclose(outfile); + return true; +} + +bool Fifo::write(uint8_t *data, size_t bytes, int64_t pts) { + if (!(outfile or open())) return false; + // Going to write a brief header + Debug(1, "Writing header ZM %lu %" PRId64, bytes, pts); + if ( fprintf(outfile, "ZM %lu %" PRId64 "\n", bytes, pts) < 0 ) { + if (errno != EAGAIN) { + Error("Problem during writing: %s", strerror(errno)); + } else { + Debug(1, "Problem during writing: %s", strerror(errno)); + } + return false; + } + if (fwrite(data, bytes, 1, outfile) != 1) { + Debug(1, "Unable to write to '%s': %s", path.c_str(), strerror(errno)); + return false; + } + return true; +} + +bool Fifo::write(std::string filename, uint8_t *data, size_t bytes) { + FILE *outfile = nullptr; + + int raw_fd = ::open(filename.c_str(), O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (raw_fd < 0) + return false; + outfile = fdopen(raw_fd, "wb"); + if (outfile == nullptr) { + ::close(raw_fd); + return false; + } + + if (fwrite(data, bytes, 1, outfile) != 1) { + Debug(1, "Unable to write to '%s': %s", filename.c_str(), strerror(errno)); + fclose(outfile); + return false; + } + + fclose(outfile); + return true; } diff --git a/src/zm_fifo.h b/src/zm_fifo.h index 065fd569c..bd7f23490 100644 --- a/src/zm_fifo.h +++ b/src/zm_fifo.h @@ -1,5 +1,5 @@ // -// ZoneMinder Fifo Debug +// ZoneMinder Fifo // Copyright (C) 2019 ZoneMinder LLC // // This program is free software; you can redistribute it and/or @@ -19,68 +19,51 @@ #ifndef ZM_FIFO_H #define ZM_FIFO_H -#if 0 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_image.h" -#endif -#include "zm_monitor.h" #include "zm_stream.h" +#include "zm_packet.h" -#define zmFifoDbgPrintf(level, params...) {\ - zmFifoDbgOutput(0, __FILE__, __LINE__, level, ##params);\ - } +class Monitor; -#ifndef ZM_DBG_OFF -#define FifoDebug(level, params...) zmFifoDbgPrintf(level, ##params) -#else -#define FifoDebug(level, params...) -#endif -void zmFifoDbgOutput( - int hex, - const char * const file, - const int line, - const int level, - const char *fstring, - ...) __attribute__((format(printf, 5, 6))); -int zmFifoDbgInit(Monitor * monitor); - -class FifoStream : public StreamBase { +class Fifo { private: - char * stream_path; - int fd; - int total_read; - int bytes_read; - unsigned int frame_count; - static void file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo = true - ); - - protected: - typedef enum { MJPEG, RAW } StreamType; - StreamType stream_type; - bool sendMJEGFrames(); - bool sendRAWFrames(); - void processCommand(const CmdMsg *msg) {} + std::string path; + bool on_blocking_abort; + FILE *outfile; + int raw_fd; public: - FifoStream() {} + static void file_create_if_missing( + const char * path, + bool is_fifo, + bool delete_fake_fifo = true + ); + + Fifo() : + on_blocking_abort(true), + outfile(nullptr), + raw_fd(-1) + {} + Fifo(const char *p_path, bool p_on_blocking_abort) : + path(p_path), + on_blocking_abort(p_on_blocking_abort), + outfile(nullptr), + raw_fd(-1) + {} + ~Fifo(); + static void fifo_create_if_missing( const char * path, bool delete_fake_fifo = true); - void setStreamStart(const char * path); - void setStreamStart(int monitor_id, const char * format); - void runStream(); + + + + static bool writePacket(std::string filename, const ZMPacket &packet); + static bool write(std::string filename, uint8_t *data, size_t size); + + bool open(); + bool close(); + + bool writePacket(const ZMPacket &packet); + bool write(uint8_t *data, size_t size, int64_t pts); }; #endif // ZM_FIFO_H diff --git a/src/zm_fifo_debug.cpp b/src/zm_fifo_debug.cpp new file mode 100644 index 000000000..bb82f5ec8 --- /dev/null +++ b/src/zm_fifo_debug.cpp @@ -0,0 +1,99 @@ +// +// ZoneMinder Fifo Debug +// Copyright (C) 2019 ZoneMinder LLC +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm_fifo_debug.h" +#include "zm_fifo.h" +#include "zm_monitor.h" +#include "zm_signal.h" +#include +#include +#include + +#define RAW_BUFFER 512 +static bool zm_fifodbg_inited = false; +FILE *zm_fifodbg_log_fd = nullptr; +char zm_fifodbg_log[PATH_MAX] = ""; + +static bool zmFifoDbgOpen() { + if ( zm_fifodbg_log_fd ) + fclose(zm_fifodbg_log_fd); + zm_fifodbg_log_fd = nullptr; + signal(SIGPIPE, SIG_IGN); + Fifo::fifo_create_if_missing(zm_fifodbg_log); + int fd = open(zm_fifodbg_log, O_WRONLY|O_NONBLOCK|O_TRUNC); + if ( fd < 0 ) + return false; + int res = flock(fd, LOCK_EX | LOCK_NB); + if ( res < 0 ) { + close(fd); + return false; + } + zm_fifodbg_log_fd = fdopen(fd, "wb"); + if ( zm_fifodbg_log_fd == nullptr ) { + close(fd); + return false; + } + return true; +} + +int zmFifoDbgInit(Monitor *monitor) { + zm_fifodbg_inited = true; + snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/dbgpipe-%u.log", + staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + zmFifoDbgOpen(); + return 1; +} + +void zmFifoDbgOutput( + int hex, + const char * const file, + const int line, + const int level, + const char *fstring, + ... + ) { + char dbg_string[8192]; + int str_size = sizeof(dbg_string); + + va_list arg_ptr; + if ( (!zm_fifodbg_inited) || ( !zm_fifodbg_log_fd && !zmFifoDbgOpen() ) ) + return; + + char *dbg_ptr = dbg_string; + va_start(arg_ptr, fstring); + if ( hex ) { + unsigned char *data = va_arg(arg_ptr, unsigned char *); + int len = va_arg(arg_ptr, int); + dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), "%d:", len); + for ( int i = 0; i < len; i++ ) { + dbg_ptr += snprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), " %02x", data[i]); + } + } else { + dbg_ptr += vsnprintf(dbg_ptr, str_size-(dbg_ptr-dbg_string), fstring, arg_ptr); + } + va_end(arg_ptr); + strncpy(dbg_ptr++, "\n", 2); + int res = fwrite(dbg_string, dbg_ptr-dbg_string, 1, zm_fifodbg_log_fd); + if ( res != 1 ) { + fclose(zm_fifodbg_log_fd); + zm_fifodbg_log_fd = nullptr; + } else { + fflush(zm_fifodbg_log_fd); + } +} diff --git a/src/zm_fifo_debug.h b/src/zm_fifo_debug.h new file mode 100644 index 000000000..08f4c4fb5 --- /dev/null +++ b/src/zm_fifo_debug.h @@ -0,0 +1,42 @@ +// +// ZoneMinder Fifo Debug +// Copyright (C) 2019 ZoneMinder LLC +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#ifndef ZM_FIFO_DEBUG_H +#define ZM_FIFO_DEBUG_H + +class Monitor; + +#define zmFifoDbgPrintf(level, params...) {\ + zmFifoDbgOutput(0, __FILE__, __LINE__, level, ##params);\ + } + +#ifndef ZM_DBG_OFF +#define FifoDebug(level, params...) zmFifoDbgPrintf(level, ##params) +#else +#define FifoDebug(level, params...) +#endif +void zmFifoDbgOutput( + int hex, + const char * const file, + const int line, + const int level, + const char *fstring, + ...) __attribute__((format(printf, 5, 6))); +int zmFifoDbgInit(Monitor * monitor); + +#endif // ZM_FIFO_DEBUG_H diff --git a/src/zm_fifo_stream.cpp b/src/zm_fifo_stream.cpp new file mode 100644 index 000000000..3035ad6d0 --- /dev/null +++ b/src/zm_fifo_stream.cpp @@ -0,0 +1,170 @@ +// +// ZoneMinder Fifo Debug +// Copyright (C) 2019 ZoneMinder LLC +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#include "zm_fifo_stream.h" +#include "zm_fifo.h" +#include "zm_monitor.h" +#include "zm_signal.h" +#include +#include +#include +#include + +#define RAW_BUFFER 512 +bool FifoStream::sendRAWFrames() { + static unsigned char buffer[RAW_BUFFER]; + int fd = open(stream_path, O_RDONLY); + if ( fd < 0 ) { + Error("Can't open %s: %s", stream_path, strerror(errno)); + return false; + } + while ( (bytes_read = read(fd, buffer, RAW_BUFFER)) ) { + if ( bytes_read == 0 ) + continue; + if ( bytes_read < 0 ) { + Error("Problem during reading: %s", strerror(errno)); + close(fd); + return false; + } + if ( fwrite(buffer, bytes_read, 1, stdout) != 1 ) { + if ( !zm_terminate ) + Error("Problem during writing: %s", strerror(errno)); + close(fd); + return false; + } + fflush(stdout); + } + close(fd); + return true; +} + +bool FifoStream::sendMJEGFrames() { + static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; + int fd = open(stream_path, O_RDONLY); + if ( fd < 0 ) { + Error("Can't open %s: %s", stream_path, strerror(errno)); + return false; + } + total_read = 0; + while ( + (bytes_read = read(fd, buffer+total_read, ZM_MAX_IMAGE_SIZE-total_read)) + ) { + if ( bytes_read < 0 ) { + Error("Problem during reading: %s", strerror(errno)); + close(fd); + return false; + } + total_read += bytes_read; + } + close(fd); + + if ( (total_read == 0) || (frame_count%frame_mod != 0) ) + return true; + + if ( fprintf(stdout, + "--" BOUNDARY "\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %d\r\n\r\n", + total_read) < 0 ) { + Error("Problem during writing: %s", strerror(errno)); + return false; + } + + if ( fwrite(buffer, total_read, 1, stdout) != 1 ) { + Error("Problem during reading: %s", strerror(errno)); + return false; + } + fprintf(stdout, "\r\n\r\n"); + fflush(stdout); + last_frame_sent = TV_2_FLOAT(now); + frame_count++; + return true; +} + +void FifoStream::setStreamStart(const char * path) { + stream_path = strdup(path); +} + +void FifoStream::setStreamStart(int monitor_id, const char * format) { + char diag_path[PATH_MAX]; + std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); + + if ( !strcmp(format, "reference") ) { + snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-r-%u.jpg", + staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + stream_type = MJPEG; + } else if ( !strcmp(format, "delta") ) { + snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-d-%u.jpg", + staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + stream_type = MJPEG; + } else { + if ( strcmp(format, "raw") ) { + Warning("Unknown or unspecified format. Defaulting to raw"); + } + snprintf(diag_path, sizeof(diag_path), "%s/dbgpipe-%u.log", + staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + stream_type = RAW; + } + + setStreamStart(diag_path); +} + +void FifoStream::runStream() { + if ( stream_type == MJPEG ) { + fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n"); + } else { + fprintf(stdout, "Content-Type: text/html\r\n\r\n"); + } + + /* only 1 person can read from a fifo at a time, so use a lock */ + char lock_file[PATH_MAX]; + snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path); + Fifo::file_create_if_missing(lock_file, false); + Debug(1, "Locking %s", lock_file); + + int fd_lock = open(lock_file, O_RDONLY); + if ( fd_lock < 0 ) { + Error("Can't open %s: %s", lock_file, strerror(errno)); + return; + } + int res = flock(fd_lock, LOCK_EX | LOCK_NB); + while ( (res < 0 and errno == EAGAIN) and (! zm_terminate) ) { + Warning("Flocking problem on %s: - %s", lock_file, strerror(errno)); + sleep(1); + res = flock(fd_lock, LOCK_EX | LOCK_NB); + } + if ( res < 0 ) { + Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file, strerror(errno)); + close(fd_lock); + return; + } + + while ( !zm_terminate ) { + gettimeofday(&now, nullptr); + checkCommandQueue(); + if ( stream_type == MJPEG ) { + if ( !sendMJEGFrames() ) + zm_terminate = true; + } else { + if ( !sendRAWFrames() ) + zm_terminate = true; + } + } + close(fd_lock); +} diff --git a/src/zm_fifo_stream.h b/src/zm_fifo_stream.h new file mode 100644 index 000000000..446108d2c --- /dev/null +++ b/src/zm_fifo_stream.h @@ -0,0 +1,57 @@ +// +// ZoneMinder Fifo Stream +// Copyright (C) 2019 ZoneMinder LLC +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +#ifndef ZM_FIFO_STREAM_H +#define ZM_FIFO_STREAM_H + +#include "zm_stream.h" + +class Monitor; + +class FifoStream : public StreamBase { + private: + char * stream_path; + int total_read; + int bytes_read; + unsigned int frame_count; + static void file_create_if_missing( + const char * path, + bool is_fifo, + bool delete_fake_fifo = true + ); + + protected: + typedef enum { UNKNOWN, MJPEG, RAW } StreamType; + StreamType stream_type; + bool sendMJEGFrames(); + bool sendRAWFrames(); + void processCommand(const CmdMsg *msg) override {} + + public: + FifoStream() : + stream_path(nullptr), + total_read(0), + bytes_read(0), + frame_count(0), + stream_type(UNKNOWN) + {} + void setStreamStart(const char * path); + void setStreamStart(int monitor_id, const char * format); + void runStream() override; +}; +#endif // ZM_FIFO_STREAM_H diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index d94fce209..af494ce38 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -17,21 +17,14 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" #include "zm_file_camera.h" +#include "zm_packet.h" +#include +#include + FileCamera::FileCamera( - int p_id, + const Monitor *monitor, const char *p_path, int p_width, int p_height, @@ -43,7 +36,7 @@ FileCamera::FileCamera( bool p_capture, bool p_record_audio) : Camera( - p_id, + monitor, FILE_SRC, p_width, p_height, @@ -94,8 +87,8 @@ int FileCamera::PreCapture() { return 0; } -int FileCamera::Capture(Image &image) { - return image.ReadJpeg(path, colours, subpixelorder)?1:-1; +int FileCamera::Capture( ZMPacket &zm_packet ) { + return zm_packet.image->ReadJpeg(path, colours, subpixelorder) ? 1 : -1; } int FileCamera::PostCapture() { diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 84201b872..11a5cfe3f 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -21,11 +21,6 @@ #define ZM_FILE_CAMERA_H #include "zm_camera.h" -#include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_packetqueue.h" - -#include // // Class representing 'file' cameras, i.e. those which are @@ -36,18 +31,29 @@ protected: char path[PATH_MAX]; public: - FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + FileCamera( + const Monitor *monitor, + const char *p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); ~FileCamera(); - const char *Path() const { return( path ); } + const char *Path() const { return path; } void Initialise(); void Terminate(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; -int Close() { return 0; }; + int PreCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; + int Close() override { return 0; }; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_font.cpp b/src/zm_font.cpp new file mode 100644 index 000000000..7ecaaa6b1 --- /dev/null +++ b/src/zm_font.cpp @@ -0,0 +1,122 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "zm_font.h" + +#include +#include + +constexpr uint8 kRequiredZmFntVersion = 1; + +constexpr uint8 FontVariant::kMaxNumCodePoints; +constexpr uint8 FontVariant::kMaxCharHeight; +constexpr uint8 FontVariant::kMaxCharWidth; + +FontVariant::FontVariant() : char_height_(0), char_width_(0), char_padding_(0), codepoint_count_(0) {} + +FontVariant::FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector bitmap) + : char_height_(char_height), char_width_(char_width), char_padding_(char_padding), bitmap_(std::move(bitmap)) { + if (char_height_ > kMaxCharHeight) { + throw std::invalid_argument("char_height > kMaxCharHeight"); + } + + if (char_width_ > kMaxCharWidth) { + throw std::invalid_argument("char_width > kMaxCharWidth"); + } + + if (bitmap_.size() % char_height_ != 0) { + throw std::invalid_argument("bitmap has wrong length"); + } + + codepoint_count_ = bitmap_.size() / char_height; +} + +nonstd::span FontVariant::GetCodepoint(uint8 idx) const { + static constexpr std::array empty_bitmap = {}; + + if (idx >= GetCodepointsCount()) { + return {empty_bitmap.begin(), GetCharHeight()}; + } + + return {bitmap_.begin() + (idx * GetCharHeight()), GetCharHeight()}; +} + +std::ifstream &operator>>(std::ifstream &stream, FontBitmapHeader &bm_header) { + stream.read(reinterpret_cast(&bm_header), sizeof(bm_header)); + + return stream; +} + +std::ifstream &operator>>(std::ifstream &stream, FontFileHeader &header) { + stream.read(header.magic, sizeof(header.magic)); + stream.read(reinterpret_cast(&header.version), sizeof(header.version)); + stream.seekg(sizeof(header.pad), std::ifstream::cur); + + for (FontBitmapHeader &bm_header : header.bitmap_header) + stream >> bm_header; + + return stream; +} + +FontLoadError ZmFont::LoadFontFile(const std::string &loc) { + std::ifstream font_file(loc, std::ifstream::binary); + font_file.exceptions(std::ifstream::badbit); + + if (!font_file.is_open()) { + return FontLoadError::kFileNotFound; + } + + FontFileHeader file_header = {}; + font_file >> file_header; + + if (font_file.fail()) { + return FontLoadError::kInvalidFile; + } + + if (memcmp(file_header.magic, "ZMFNT", 5) != 0 || file_header.version != kRequiredZmFntVersion) { + return FontLoadError::kInvalidFile; + } + + for (int i = 0; i < kNumFontSizes; i++) { + FontBitmapHeader bitmap_header = file_header.bitmap_header[i]; + + if (bitmap_header.char_width > FontVariant::kMaxCharWidth + || bitmap_header.char_height > FontVariant::kMaxCharHeight + || bitmap_header.number_of_code_points > FontVariant::kMaxNumCodePoints) { + return FontLoadError::kInvalidFile; + } + + std::vector bitmap; + bitmap.resize(bitmap_header.number_of_code_points * bitmap_header.char_height); + + std::size_t bitmap_bytes = bitmap.size() * sizeof(uint64); + font_file.read(reinterpret_cast(bitmap.data()), static_cast(bitmap_bytes)); + + variants_[i] = + {bitmap_header.char_height, bitmap_header.char_width, bitmap_header.char_padding, std::move(bitmap)}; + } + + if (font_file.fail()) { + return FontLoadError::kInvalidFile; + } + + return FontLoadError::kOk; +} + +const FontVariant &ZmFont::GetFontVariant(uint8 idx) const { + return variants_.at(idx); +} diff --git a/src/zm_font.h b/src/zm_font.h index cdad72dc4..b606108b2 100644 --- a/src/zm_font.h +++ b/src/zm_font.h @@ -1,3337 +1,93 @@ -/**********************************************/ -/* */ -/* Font file generated by rthelen */ -/* */ -/**********************************************/ - -static unsigned char fontdata[] = { - - /* 0 0x00 '^A' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 1 0x01 '^B' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 2 0x02 '^C' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 3 0x03 '^D' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 4 0x04 '^E' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 5 0x05 '^F' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 6 0x06 '^G' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 7 0x07 '^H' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 8 0x08 '^I' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 9 0x09 '^J' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 10 0x0a '^K' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 11 0x0b '^L' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 12 0x0c '^M' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 13 0x0d '^N' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 14 0x0e '^O' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 15 0x0f '^P' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 16 0x10 '^Q' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 17 0x11 '^R' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 18 0x12 '^S' */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 19 0x13 '^T' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x7c, /* 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 20 0x14 '^U' */ - 0x18, /* 000 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x78, /* 0 000 */ - 0x78, /* 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 21 0x15 '^V' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 22 0x16 '^W' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 23 0x17 '^X' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 24 0x18 '^Y' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 25 0x19 '^Z' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 26 0x1a '^[' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 27 0x1b '^\' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 28 0x1c '^]' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 29 0x1d '^^' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 30 0x1e '^_' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 31 0x1f '^`' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 32 0x20 ' ' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 33 0x21 '!' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 34 0x22 '"' */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 35 0x23 '#' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 36 0x24 '$' */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x38, /* 00 000 */ - 0x14, /* 000 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 37 0x25 '%' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x58, /* 0 0 000 */ - 0x28, /* 00 0 000 */ - 0x34, /* 00 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x48, /* 0 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 38 0x26 '&' */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x54, /* 0 0 0 00 */ - 0x48, /* 0 00 000 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 39 0x27 ''' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 40 0x28 '(' */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 41 0x29 ')' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 42 0x2a '*' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 43 0x2b '+' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 44 0x2c ',' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 45 0x2d '-' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 46 0x2e '.' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 47 0x2f '/' */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 48 0x30 '0' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 49 0x31 '1' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x18, /* 000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 50 0x32 '2' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 51 0x33 '3' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x18, /* 000 000 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 52 0x34 '4' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x18, /* 000 000 */ - 0x28, /* 00 0 000 */ - 0x48, /* 0 00 000 */ - 0x7c, /* 0 00 */ - 0x08, /* 0000 000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 53 0x35 '5' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 54 0x36 '6' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 55 0x37 '7' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 56 0x38 '8' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 57 0x39 '9' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 58 0x3a ':' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 59 0x3b ';' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 60 0x3c '<' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 61 0x3d '=' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 62 0x3e '>' */ - 0x00, /* 00000000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 63 0x3f '?' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 64 0x40 '@' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x74, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 65 0x41 'A' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 66 0x42 'B' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 67 0x43 'C' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 68 0x44 'D' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 69 0x45 'E' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 70 0x46 'F' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 71 0x47 'G' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 72 0x48 'H' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 73 0x49 'I' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 74 0x4a 'J' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 75 0x4b 'K' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x60, /* 0 00000 */ - 0x50, /* 0 0 0000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 76 0x4c 'L' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 77 0x4d 'M' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x6c, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 78 0x4e 'N' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 79 0x4f 'O' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 80 0x50 'P' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 81 0x51 'Q' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 82 0x52 'R' */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 83 0x53 'S' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 84 0x54 'T' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 85 0x55 'U' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 86 0x56 'V' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 87 0x57 'W' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x6c, /* 0 0 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 88 0x58 'X' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 89 0x59 'Y' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 90 0x5a 'Z' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 91 0x5b '[' */ - 0x0c, /* 0000 00 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x0c, /* 0000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 92 0x5c '\' */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x02, /* 000000 0 */ - 0x02, /* 000000 0 */ - 0x00, /* 00000000 */ - - /* 93 0x5d ']' */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x30, /* 00 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 94 0x5e '^' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 95 0x5f '_' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 96 0x60 '`' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 97 0x61 'a' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 98 0x62 'b' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 99 0x63 'c' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 100 0x64 'd' */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 101 0x65 'e' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 102 0x66 'f' */ - 0x00, /* 00000000 */ - 0x0c, /* 0000 00 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 103 0x67 'g' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 104 0x68 'h' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 105 0x69 'i' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 106 0x6a 'j' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 107 0x6b 'k' */ - 0x00, /* 00000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x48, /* 0 00 000 */ - 0x50, /* 0 0 0000 */ - 0x70, /* 0 0000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 108 0x6c 'l' */ - 0x00, /* 00000000 */ - 0x30, /* 00 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 109 0x6d 'm' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 110 0x6e 'n' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 111 0x6f 'o' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 112 0x70 'p' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x78, /* 0 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 113 0x71 'q' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - - /* 114 0x72 'r' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 115 0x73 's' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x40, /* 0 000000 */ - 0x38, /* 00 000 */ - 0x04, /* 00000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 116 0x74 't' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x0c, /* 0000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 117 0x75 'u' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 118 0x76 'v' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 119 0x77 'w' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 120 0x78 'x' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 121 0x79 'y' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 122 0x7a 'z' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 123 0x7b '{' */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - - /* 124 0x7c '|' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 125 0x7d '}' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 126 0x7e '~' */ - 0x00, /* 00000000 */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 127 0x7f '^?' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 128 0x80 '\200' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 129 0x81 '\201' */ - 0x28, /* 00 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 130 0x82 '\202' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 131 0x83 '\203' */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x78, /* 0 000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 132 0x84 '\204' */ - 0x58, /* 0 0 000 */ - 0x44, /* 0 000 00 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x4c, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 133 0x85 '\205' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 134 0x86 '\206' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 135 0x87 '\207' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 136 0x88 '\210' */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 137 0x89 '\211' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 138 0x8a '\212' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 139 0x8b '\213' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 140 0x8c '\214' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x3c, /* 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 141 0x8d '\215' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - - /* 142 0x8e '\216' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 143 0x8f '\217' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 144 0x90 '\220' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 145 0x91 '\221' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x40, /* 0 000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 146 0x92 '\222' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 147 0x93 '\223' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 148 0x94 '\224' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 149 0x95 '\225' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 150 0x96 '\226' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x58, /* 0 0 000 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 151 0x97 '\227' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 152 0x98 '\230' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 153 0x99 '\231' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 154 0x9a '\232' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 155 0x9b '\233' */ - 0x34, /* 00 0 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 156 0x9c '\234' */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 157 0x9d '\235' */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 158 0x9e '\236' */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 159 0x9f '\237' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x34, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 160 0xa0 '\240' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 161 0xa1 '\241' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 162 0xa2 '\242' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x54, /* 0 0 0 00 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 163 0xa3 '\243' */ - 0x30, /* 00 0000 */ - 0x48, /* 0 00 000 */ - 0x40, /* 0 000000 */ - 0x70, /* 0 0000 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x78, /* 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 164 0xa4 '\244' */ - 0x44, /* 0 000 00 */ - 0x24, /* 00 00 00 */ - 0x50, /* 0 0 0000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x14, /* 000 0 00 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 165 0xa5 '\245' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x7c, /* 0 00 */ - 0x7c, /* 0 00 */ - 0x7c, /* 0 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 166 0xa6 '\246' */ - 0x3c, /* 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x3c, /* 00 00 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 167 0xa7 '\247' */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x44, /* 0 000 00 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x58, /* 0 0 000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 168 0xa8 '\250' */ - 0x00, /* 00000000 */ - 0x70, /* 0 0000 */ - 0x08, /* 0000 000 */ - 0x64, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x58, /* 0 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 169 0xa9 '\251' */ - 0x00, /* 00000000 */ - 0x70, /* 0 0000 */ - 0x08, /* 0000 000 */ - 0x34, /* 00 0 00 */ - 0x44, /* 0 000 00 */ - 0x34, /* 00 0 00 */ - 0x08, /* 0000 000 */ - 0x70, /* 0 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 170 0xaa '\252' */ - 0x00, /* 00000000 */ - 0x7a, /* 0 0 0 */ - 0x2e, /* 00 0 0 */ - 0x2e, /* 00 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 171 0xab '\253' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 172 0xac '\254' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 173 0xad '\255' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 174 0xae '\256' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x78, /* 0 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x5c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 175 0xaf '\257' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 176 0xb0 '\260' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x6c, /* 0 0 00 */ - 0x54, /* 0 0 0 00 */ - 0x6c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 177 0xb1 '\261' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 178 0xb2 '\262' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 179 0xb3 '\263' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x04, /* 00000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 180 0xb4 '\264' */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x7c, /* 0 00 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 181 0xb5 '\265' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x74, /* 0 0 00 */ - 0x40, /* 0 000000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - - /* 182 0xb6 '\266' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x0c, /* 0000 00 */ - 0x14, /* 000 0 00 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 183 0xb7 '\267' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x24, /* 00 00 00 */ - 0x10, /* 000 0000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x24, /* 00 00 00 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 184 0xb8 '\270' */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 185 0xb9 '\271' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 186 0xba '\272' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 187 0xbb '\273' */ - 0x00, /* 00000000 */ - 0x1c, /* 000 00 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x1c, /* 000 00 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 188 0xbc '\274' */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 189 0xbd '\275' */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x6c, /* 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 190 0xbe '\276' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x54, /* 0 0 0 00 */ - 0x5c, /* 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 191 0xbf '\277' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x4c, /* 0 00 00 */ - 0x54, /* 0 0 0 00 */ - 0x64, /* 0 00 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 192 0xc0 '\300' */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x20, /* 00 00000 */ - 0x40, /* 0 000000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 193 0xc1 '\301' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x08, /* 0000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 194 0xc2 '\302' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x04, /* 00000 00 */ - 0x04, /* 00000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 195 0xc3 '\303' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x0c, /* 0000 00 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x50, /* 0 0 0000 */ - 0x20, /* 00 00000 */ - 0x20, /* 00 00000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 196 0xc4 '\304' */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x60, /* 0 00000 */ - 0x00, /* 00000000 */ - - /* 197 0xc5 '\305' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x40, /* 0 000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 198 0xc6 '\306' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 199 0xc7 '\307' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x24, /* 00 00 00 */ - 0x48, /* 0 00 000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 200 0xc8 '\310' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x48, /* 0 00 000 */ - 0x24, /* 00 00 00 */ - 0x24, /* 00 00 00 */ - 0x48, /* 0 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 201 0xc9 '\311' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x54, /* 0 0 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 202 0xca '\312' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 203 0xcb '\313' */ - 0x10, /* 000 0000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 204 0xcc '\314' */ - 0x58, /* 0 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x7c, /* 0 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 205 0xcd '\315' */ - 0x58, /* 0 0 000 */ - 0x38, /* 00 000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 206 0xce '\316' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x58, /* 0 0 000 */ - 0x50, /* 0 0 0000 */ - 0x50, /* 0 0 0000 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 207 0xcf '\317' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x54, /* 0 0 0 00 */ - 0x5c, /* 0 0 00 */ - 0x50, /* 0 0 0000 */ - 0x2c, /* 00 0 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 208 0xd0 '\320' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 209 0xd1 '\321' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 210 0xd2 '\322' */ - 0x00, /* 00000000 */ - 0x14, /* 000 0 00 */ - 0x28, /* 00 0 000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 211 0xd3 '\323' */ - 0x00, /* 00000000 */ - 0x14, /* 000 0 00 */ - 0x14, /* 000 0 00 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 212 0xd4 '\324' */ - 0x00, /* 00000000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x18, /* 000 000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 213 0xd5 '\325' */ - 0x00, /* 00000000 */ - 0x18, /* 000 000 */ - 0x08, /* 0000 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 214 0xd6 '\326' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x7c, /* 0 00 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 215 0xd7 '\327' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x10, /* 000 0000 */ - 0x28, /* 00 0 000 */ - 0x44, /* 0 000 00 */ - 0x28, /* 00 0 000 */ - 0x10, /* 000 0000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 216 0xd8 '\330' */ - 0x00, /* 00000000 */ - 0x28, /* 00 0 000 */ - 0x00, /* 00000000 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x44, /* 0 000 00 */ - 0x3c, /* 00 00 */ - 0x04, /* 00000 00 */ - 0x38, /* 00 000 */ - 0x00, /* 00000000 */ - - /* 217 0xd9 '\331' */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x7e, /* 0 0 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 218 0xda '\332' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 219 0xdb '\333' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 220 0xdc '\334' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 221 0xdd '\335' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 222 0xde '\336' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 223 0xdf '\337' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 224 0xe0 '\340' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 225 0xe1 '\341' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 226 0xe2 '\342' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 227 0xe3 '\343' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 228 0xe4 '\344' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 229 0xe5 '\345' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 230 0xe6 '\346' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 231 0xe7 '\347' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 232 0xe8 '\350' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 233 0xe9 '\351' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 234 0xea '\352' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 235 0xeb '\353' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 236 0xec '\354' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 237 0xed '\355' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 238 0xee '\356' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 239 0xef '\357' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 240 0xf0 '\360' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 241 0xf1 '\361' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 242 0xf2 '\362' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 243 0xf3 '\363' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 244 0xf4 '\364' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 245 0xf5 '\365' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 246 0xf6 '\366' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 247 0xf7 '\367' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 248 0xf8 '\370' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 249 0xf9 '\371' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 250 0xfa '\372' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 251 0xfb '\373' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 252 0xfc '\374' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 253 0xfd '\375' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 254 0xfe '\376' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - - /* 255 0xff '\377' */ - 0x00, /* 00000000 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x3c, /* 00 00 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - 0x00, /* 00000000 */ - +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ZM_FONT_H +#define ZM_FONT_H + +#include "zm_define.h" +#include +#include +#include "span.hpp" +#include +#include + +constexpr uint8 kNumFontSizes = 4; + +enum class FontLoadError { + kOk, + kFileNotFound, + kInvalidFile }; + +#pragma pack(push, 1) +struct FontBitmapHeader { + uint16 char_height; // height of every character + uint16 char_width; // width of every character + uint32 number_of_code_points; // number of codepoints; max. 255 for now + uint32 idx; // offset in data where data for the bitmap starts; not used + uint8 char_padding; // padding around characters + uint8 pad[3]; // struct padding +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct FontFileHeader { + char magic[6]; // "ZMFNT\0" + uint8 version; + uint8 pad; + std::array bitmap_header; +}; +#pragma pack(pop) + +class FontVariant { + public: + static constexpr uint8 kMaxNumCodePoints = 255; + // height cannot be greater than 200 (arbitrary number; shouldn't need more than this) + static constexpr uint8 kMaxCharHeight = 200; + // character width can't be greater than 64 as a row is represented as an uint64 + static constexpr uint8 kMaxCharWidth = 64; + + FontVariant(); + FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector bitmap); + + uint16 GetCharHeight() const { return char_height_; } + uint16 GetCharWidth() const { return char_width_; } + uint8 GetCharPadding() const { return char_padding_; } + uint8 GetCodepointsCount() const { return codepoint_count_; } + + // Returns the bitmap of the codepoint `idx`. If `idx` is greater than `GetCodepointsCount` + // a all-zero bitmap with `GetCharHeight` elements is returned. + nonstd::span GetCodepoint(uint8 idx) const; + + private: + uint16 char_height_; + uint16 char_width_; + uint8 char_padding_; + uint8 codepoint_count_; + std::vector bitmap_; +}; + +class ZmFont { + public: + FontLoadError LoadFontFile(const std::string &loc); + const FontVariant &GetFontVariant(uint8 idx) const; + + private: + std::array variants_; +}; + +#endif diff --git a/src/zm_frame.cpp b/src/zm_frame.cpp index bf47bdbf5..f6c0e296d 100644 --- a/src/zm_frame.cpp +++ b/src/zm_frame.cpp @@ -1,18 +1,16 @@ #include "zm_frame.h" -Frame::Frame( - event_id_t p_event_id, - int p_frame_id, - FrameType p_type, - struct timeval p_timestamp, - struct DeltaTimeval p_delta, - int p_score - ) : - event_id(p_event_id), +Frame::Frame(event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + struct timeval p_timestamp, + struct DeltaTimeval &p_delta, + int p_score, + std::vector p_stats) + : event_id(p_event_id), frame_id(p_frame_id), type(p_type), timestamp(p_timestamp), delta(p_delta), - score(p_score) -{ -} + score(p_score), + zone_stats(std::move(p_stats)) {} diff --git a/src/zm_frame.h b/src/zm_frame.h index 84190e779..8f5a7e9cc 100644 --- a/src/zm_frame.h +++ b/src/zm_frame.h @@ -20,35 +20,40 @@ #ifndef ZM_FRAME_H #define ZM_FRAME_H -#include -#include -class Frame; - #include "zm_event.h" #include "zm_time.h" +#include "zm_zone.h" + +#include +#include + +enum FrameType { + NORMAL = 0, + BULK, + ALARM +}; // // This describes a frame record // class Frame { + public: + Frame(event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + struct timeval p_timestamp, + struct DeltaTimeval &p_delta, + int p_score, + std::vector p_stats + ); -public: - Frame( - event_id_t p_event_id, - int p_frame_id, - FrameType p_type, - struct timeval p_timestamp, - struct DeltaTimeval p_delta, - int p_score - ); - - event_id_t event_id; - int frame_id; - FrameType type; + event_id_t event_id; + int frame_id; + FrameType type; struct timeval timestamp; - struct DeltaTimeval delta; + struct DeltaTimeval delta; int score; - + std::vector zone_stats; }; #endif // ZM_FRAME_H diff --git a/src/zm_group.cpp b/src/zm_group.cpp index 070d3b571..b2fc43e5f 100644 --- a/src/zm_group.cpp +++ b/src/zm_group.cpp @@ -15,15 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm.h" -#include "zm_db.h" +*/ #include "zm_group.h" -#include -#include +#include "zm_logger.h" +#include Group::Group() { Warning("Instantiating default Group Object. Should not happen."); @@ -33,7 +30,7 @@ Group::Group() { } // The order of columns is: Id, ParentId, Name -Group::Group(MYSQL_ROW &dbrow) { +Group::Group(const MYSQL_ROW &dbrow) { unsigned int index = 0; id = atoi(dbrow[index++]); parent_id = dbrow[index] ? atoi(dbrow[index]): 0; index++; @@ -46,11 +43,11 @@ Group::Group(unsigned int p_id) { if ( p_id ) { char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%d", p_id); - Debug(2,"Loading Group for %d using %s", p_id, sql); + snprintf(sql, sizeof(sql), "SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id); + Debug(2,"Loading Group for %u using %s", p_id, sql); zmDbRow dbrow; if ( !dbrow.fetch(sql) ) { - Error("Unable to load group for id %d: %s", p_id, mysql_error(&dbconn)); + Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn)); } else { unsigned int index = 0; id = atoi(dbrow[index++]); diff --git a/src/zm_group.h b/src/zm_group.h index 6adb8cd93..f3ce1f3cf 100644 --- a/src/zm_group.h +++ b/src/zm_group.h @@ -17,11 +17,11 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "zm_db.h" - #ifndef ZM_GROUP_H #define ZM_GROUP_H +#include "zm_db.h" + class Group { protected: @@ -31,7 +31,7 @@ protected: public: Group(); - explicit Group( MYSQL_ROW &dbrow ); + explicit Group(const MYSQL_ROW &dbrow ); explicit Group( unsigned int p_id ); ~Group(); diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 0a4cca08f..909b4db15 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -16,17 +16,16 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" -#include "zm_font.h" -#include "zm_bigfont.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_rgb.h" -#include "zm_ffmpeg.h" +#include "zm_image.h" + +#include "zm_font.h" +#include "zm_poly.h" +#include "zm_utils.h" +#include #include #include -#include +#include static unsigned char y_table_global[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 78, 79, 80, 81, 82, 83, 85, 86, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 119, 121, 122, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 160, 161, 163, 164, 165, 166, 167, 168, 170, 171, 172, 173, 174, 175, 176, 178, 179, 180, 181, 182, 183, 185, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 213, 214, 215, 216, 217, 218, 220, 221, 222, 223, 224, 225, 227, 228, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; @@ -78,6 +77,9 @@ static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8; /* Pointer to image buffer memory copy function */ imgbufcpy_fptr_t fptr_imgbufcpy; +/* Font */ +static ZmFont font; + void Image::update_function_pointers() { /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ if ( pixels % 16 || pixels % 12 ) { @@ -104,21 +106,30 @@ void Image::update_function_pointers() { } // This constructor is not used anywhere -Image::Image() { +Image::Image() : + delta8_rgb(&std_delta8_rgb), + delta8_bgr(&std_delta8_bgr), + delta8_rgba(&std_delta8_rgba), + delta8_bgra(&std_delta8_bgra), + delta8_argb(&std_delta8_argb), + delta8_abgr(&std_delta8_abgr), + delta8_gray8(&std_delta8_gray8), + blend(&std_blend) +{ if ( !initialised ) Initialise(); width = 0; linesize = 0; height = 0; + padding = 0; pixels = 0; colours = 0; subpixelorder = 0; size = 0; allocation = 0; buffer = 0; - buffertype = 0; + buffertype = ZM_BUFTYPE_DONTFREE; holdbuffer = 0; - text[0] = '\0'; blend = fptr_blend; } @@ -128,54 +139,62 @@ Image::Image(const char *filename) { width = 0; linesize = 0; height = 0; + padding = 0; pixels = 0; colours = 0; subpixelorder = 0; size = 0; allocation = 0; buffer = 0; - buffertype = 0; + buffertype = ZM_BUFTYPE_DONTFREE; holdbuffer = 0; ReadJpeg(filename, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); - text[0] = '\0'; update_function_pointers(); } -Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) { - if ( !initialised ) +Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) : + width(p_width), + height(p_height), + colours(p_colours), + padding(p_padding), + subpixelorder(p_subpixelorder), + buffer(p_buffer) { + + if (!initialised) Initialise(); - width = p_width; - height = p_height; - pixels = width*height; - colours = p_colours; + pixels = width * height; linesize = p_width * p_colours; - padding = p_padding; - subpixelorder = p_subpixelorder; - size = linesize*height + padding; + size = linesize * height + padding; buffer = nullptr; holdbuffer = 0; - if ( p_buffer ) { + if (p_buffer) { allocation = size; buffertype = ZM_BUFTYPE_DONTFREE; buffer = p_buffer; } else { AllocImgBuffer(size); } - text[0] = '\0'; + if (!subpixelorder and colours>1) { + // Default to RGBA when no subpixelorder is specified. + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + imagePixFormat = AVPixFormat(); update_function_pointers(); } -Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) { +Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) : + width(p_width), + linesize(p_linesize), + height(p_height), + colours(p_colours), + padding(p_padding), + subpixelorder(p_subpixelorder), + buffer(p_buffer) +{ if ( !initialised ) Initialise(); - width = p_width; - linesize = p_linesize; - height = p_height; pixels = width*height; - colours = p_colours; - padding = p_padding; - subpixelorder = p_subpixelorder; size = linesize*height + padding; buffer = nullptr; holdbuffer = 0; @@ -186,69 +205,131 @@ Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_sub } else { AllocImgBuffer(size); } - text[0] = '\0'; + if (!subpixelorder and colours>1) { + // Default to RGBA when no subpixelorder is specified. + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + imagePixFormat = AVPixFormat(); update_function_pointers(); } Image::Image(const AVFrame *frame) { - AVFrame *dest_frame = zm_av_frame_alloc(); - text[0] = '\0'; - width = frame->width; height = frame->height; pixels = width*height; + + zm_dump_video_frame(frame, "Image.Assign(frame)"); + // FIXME colours = ZM_COLOUR_RGB32; subpixelorder = ZM_SUBPIX_ORDER_RGBA; + imagePixFormat = AV_PIX_FMT_RGBA; + //(AVPixelFormat)frame->format; - #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 32); // av_image_get_linesize isn't aligned, so we have to do that. linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 32); #else linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 1); - size = avpicture_get_size(AV_PIX_FMT_RGBA, width, height); + size = avpicture_get_size(AV_PIX_FMT_RGB0, width, height); #endif + padding = 0; buffer = nullptr; holdbuffer = 0; AllocImgBuffer(size); + this->Assign(frame); +} +static void dont_free(void *opaque, uint8_t *data) { +} + +int Image::PopulateFrame(AVFrame *frame) { + Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d %s", + width, height, linesize, colours, size, + av_get_pix_fmt_name(imagePixFormat) + ); + AVBufferRef *ref = av_buffer_create(buffer, size, + dont_free, /* Free callback */ + nullptr, /* opaque */ + 0 /* flags */ + ); + if (!ref) { + Warning("Failed to create av_buffer"); + } + frame->buf[0] = ref; #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(dest_frame->data, dest_frame->linesize, - buffer, AV_PIX_FMT_RGBA, width, height, 32); + // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. + int size = av_image_fill_arrays( + frame->data, frame->linesize, + buffer, imagePixFormat, width, height, + 32 //alignment + ); + if ( size < 0 ) { + Error("Problem setting up data pointers into image %s", + av_make_error_string(size).c_str()); + return size; + } #else - avpicture_fill( (AVPicture *)dest_frame, buffer, - AV_PIX_FMT_RGBA, width, height); + avpicture_fill((AVPicture *)frame, buffer, + imagePixFormat, width, height); #endif + frame->width = width; + frame->height = height; + frame->format = imagePixFormat; + Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size); + zm_dump_video_frame(frame, "Image.Populate(frame)"); + return 1; +} // int Image::PopulateFrame(AVFrame *frame) -#if HAVE_LIBSWSCALE +bool Image::Assign(const AVFrame *frame) { + /* Assume the dimensions etc are correct. FIXME */ + + // Desired format + AVPixelFormat format = (AVPixelFormat)AVPixFormat(); + AVFrame *dest_frame = zm_av_frame_alloc(); sws_convert_context = sws_getCachedContext( sws_convert_context, - width, - height, - (AVPixelFormat)frame->format, - width, height, - AV_PIX_FMT_RGBA, SWS_BICUBIC, nullptr, - nullptr, nullptr); - if ( sws_convert_context == nullptr ) - Fatal("Unable to create conversion context"); - - if ( sws_scale(sws_convert_context, frame->data, frame->linesize, 0, frame->height, - dest_frame->data, dest_frame->linesize) < 0 ) - Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA); -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE + frame->width, frame->height, (AVPixelFormat)frame->format, + width, height, format, + SWS_BICUBIC, + //SWS_POINT | SWS_BITEXACT, + nullptr, nullptr, nullptr); + if (sws_convert_context == nullptr) { + Error("Unable to create conversion context"); + return false; + } + bool result = Assign(frame, sws_convert_context, dest_frame); av_frame_free(&dest_frame); update_function_pointers(); -} // end Image::Image(const AVFrame *frame) + return result; +} // end Image::Assign(const AVFrame *frame) + +bool Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame) { + PopulateFrame(temp_frame); + zm_dump_video_frame(frame, "source frame before convert"); + temp_frame->pts = frame->pts; + AVPixelFormat format = (AVPixelFormat)AVPixFormat(); + + if (sws_scale(convert_context, + frame->data, frame->linesize, 0, frame->height, + temp_frame->data, temp_frame->linesize) < 0) { + Error("Unable to convert raw format %u %ux%u to target format %u %ux%u", + frame->format, frame->width, frame->height, + format, width, height); + return false; + } + zm_dump_video_frame(temp_frame, "dest frame after convert"); + return true; +} // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame) Image::Image(const Image &p_image) { if ( !initialised ) Initialise(); width = p_image.width; linesize = p_image.linesize; + padding = 0; height = p_image.height; pixels = p_image.pixels; colours = p_image.colours; @@ -258,7 +339,8 @@ Image::Image(const Image &p_image) { holdbuffer = 0; AllocImgBuffer(size); (*fptr_imgbufcpy)(buffer, p_image.buffer, size); - strncpy(text, p_image.text, sizeof(text)); + annotation_ = p_image.annotation_; + imagePixFormat = p_image.imagePixFormat; update_function_pointers(); } @@ -268,27 +350,32 @@ Image::~Image() { /* Should be called as part of program shutdown to free everything */ void Image::Deinitialise() { - if ( !initialised ) return; + if (!initialised) return; initialised = false; - if ( readjpg_dcinfo ) { + if (readjpg_dcinfo) { jpeg_destroy_decompress(readjpg_dcinfo); delete readjpg_dcinfo; readjpg_dcinfo = nullptr; } - if ( decodejpg_dcinfo ) { + if (decodejpg_dcinfo) { jpeg_destroy_decompress(decodejpg_dcinfo); delete decodejpg_dcinfo; decodejpg_dcinfo = nullptr; } - for ( unsigned int quality=0; quality <= 100; quality += 1 ) { - if ( writejpg_ccinfo[quality] ) { + for (unsigned int quality=0; quality <= 100; quality += 1) { + if (writejpg_ccinfo[quality]) { jpeg_destroy_compress(writejpg_ccinfo[quality]); delete writejpg_ccinfo[quality]; writejpg_ccinfo[quality] = nullptr; } + if (encodejpg_ccinfo[quality]) { + jpeg_destroy_compress(encodejpg_ccinfo[quality]); + delete encodejpg_ccinfo[quality]; + encodejpg_ccinfo[quality] = nullptr; + } } // end foreach quality - if ( sws_convert_context ) { + if (sws_convert_context) { sws_freeContext(sws_convert_context); sws_convert_context = nullptr; } @@ -485,6 +572,12 @@ void Image::Initialise() { g_u_table = g_u_table_global; b_u_table = b_u_table_global; + FontLoadError res = font.LoadFontFile(config.font_file_location); + if ( res == FontLoadError::kFileNotFound ) { + Panic("Invalid font location: %s", config.font_file_location); + } else if ( res == FontLoadError::kInvalidFile ) { + Panic("Invalid font file."); + } initialised = true; } @@ -563,11 +656,11 @@ void Image::AssignDirect( } if ( p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32 ) { - Error("Attempt to directly assign buffer with unexpected colours per pixel: %d",p_colours); + Error("Attempt to directly assign buffer with unexpected colours per pixel: %d", p_colours); return; } - unsigned int new_buffer_size = ((p_width*p_height)*p_colours); + size_t new_buffer_size = p_width * p_height * p_colours; if ( buffer_size < new_buffer_size ) { Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d*%d colours = %zu", @@ -580,38 +673,28 @@ void Image::AssignDirect( Error("Held buffer is undersized for assigned buffer"); return; } else { - width = p_width; - height = p_height; - colours = p_colours; - linesize = width * colours; - subpixelorder = p_subpixelorder; - pixels = height*width; - size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - /* Copy into the held buffer */ if ( new_buffer != buffer ) { (*fptr_imgbufcpy)(buffer, new_buffer, size); } - /* Free the new buffer */ DumpBuffer(new_buffer, p_buffertype); } } else { /* Free an existing buffer if any */ DumpImgBuffer(); - - width = p_width; - height = p_height; - colours = p_colours; - linesize = width*colours; - subpixelorder = p_subpixelorder; - pixels = height*width; - size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - allocation = buffer_size; buffertype = p_buffertype; buffer = new_buffer; } + + width = p_width; + height = p_height; + colours = p_colours; + linesize = width * colours; + subpixelorder = p_subpixelorder; + pixels = width * height; + size = new_buffer_size; } // end void Image::AssignDirect void Image::Assign( @@ -621,13 +704,13 @@ void Image::Assign( const unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size) { - unsigned int new_size = (p_width * p_height) * p_colours; if ( new_buffer == nullptr ) { Error("Attempt to assign buffer from a NULL pointer"); return; } + unsigned int new_size = p_width * p_height * p_colours; if ( buffer_size < new_size ) { Error("Attempt to assign buffer from an undersized buffer of size: %zu", buffer_size); return; @@ -651,7 +734,7 @@ void Image::Assign( return; } } else { - if ( new_size > allocation || !buffer ) { + if ( (new_size > allocation) || !buffer ) { DumpImgBuffer(); AllocImgBuffer(new_size); } @@ -667,6 +750,7 @@ void Image::Assign( if ( new_buffer != buffer ) (*fptr_imgbufcpy)(buffer, new_buffer, size); + update_function_pointers(); } void Image::Assign(const Image &image) { @@ -689,7 +773,6 @@ void Image::Assign(const Image &image) { if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder - || image.linesize != linesize ) { if ( holdbuffer && buffer ) { @@ -698,7 +781,7 @@ void Image::Assign(const Image &image) { return; } } else { - if ( new_size > allocation || !buffer) { + if ( new_size > allocation || !buffer ) { // DumpImgBuffer(); This is also done in AllocImgBuffer AllocImgBuffer(new_size); } @@ -886,8 +969,8 @@ bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int jpeg_read_header(cinfo, TRUE); - if ( cinfo->num_components != 1 && cinfo->num_components != 3 ) { - Error( "Unexpected colours when reading jpeg image: %d", colours ); + if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { + Error("Unexpected colours when reading jpeg image: %d", colours); jpeg_abort_decompress(cinfo); fclose(infile); return false; @@ -902,7 +985,7 @@ bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int new_width = cinfo->image_width; new_height = cinfo->image_height; - if ( width != new_width || height != new_height ) { + if ( (width != new_width) || (height != new_height) ) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height); } @@ -1012,8 +1095,7 @@ bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; FILE *outfile = nullptr; - static int raw_fd = 0; - raw_fd = 0; + int raw_fd = 0; if ( !cinfo ) { cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; @@ -1106,7 +1188,7 @@ cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif - */ +*/ cinfo->in_color_space = JCS_RGB; } break; @@ -1117,8 +1199,8 @@ cinfo->out_color_space = JCS_RGB; cinfo->dct_method = JDCT_FASTEST; jpeg_start_compress(cinfo, TRUE); - if ( config.add_jpeg_comments && text[0] ) { - jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)text, strlen(text)); + if ( config.add_jpeg_comments && !annotation_.empty() ) { + jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast(annotation_.c_str()), annotation_.size()); } // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal // No timestamp just leave off the exif section. @@ -1131,7 +1213,8 @@ cinfo->out_color_space = JCS_RGB; // This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64]; char timebuf[64], msbuf[64]; - strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); + tm timestamp_tm = {}; + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(×tamp.tv_sec, ×tamp_tm)); snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it unsigned char exiftimes[82] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, @@ -1197,7 +1280,7 @@ bool Image::DecodeJpeg( new_width = cinfo->image_width; new_height = cinfo->image_height; - if ( width != new_width || height != new_height ) { + if ( (width != new_width) || (height != new_height) ) { Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height); } @@ -1406,23 +1489,21 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig lo_x, lo_y, hi_x, hi_y, width-1, height-1); return false; } - - if ( new_width == width && new_height == height ) { + if ( (new_width == width) && (new_height == height) ) { return true; } - unsigned int new_size = new_width*new_height*colours; + unsigned int new_stride = new_width * colours; + unsigned int new_size = new_stride * new_height; uint8_t *new_buffer = AllocBuffer(new_size); - unsigned int new_stride = new_width * colours; for ( unsigned int y = lo_y, ny = 0; y <= hi_y; y++, ny++ ) { - unsigned char *pbuf = &buffer[((y*linesize)+lo_x)]; - unsigned char *pnbuf = &new_buffer[(ny*new_width)*colours]; - memcpy( pnbuf, pbuf, new_stride ); + unsigned char *pbuf = &buffer[((y*linesize)+(lo_x*colours))]; + unsigned char *pnbuf = &new_buffer[ny*new_stride]; + memcpy(pnbuf, pbuf, new_stride); } AssignDirect(new_width, new_height, colours, subpixelorder, new_buffer, new_size, ZM_BUFTYPE_ZM); - return true; } @@ -1439,7 +1520,8 @@ void Image::Overlay( const Image &image ) { } if ( colours == image.colours && subpixelorder != image.subpixelorder ) { - Warning("Attempt to overlay images of same format but with different subpixel order."); + Warning("Attempt to overlay images of same format but with different subpixel order %d != %d.", + subpixelorder, image.subpixelorder); } /* Grayscale ontop of grayscale - complete */ @@ -1678,7 +1760,7 @@ void Image::Blend( const Image &image, int transparency ) { #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); - timespec_diff(&start,&end,&diff); + TimespecDiff(&start,&end,&diff); executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)size)/((((long double)executetime)/1000)); @@ -1784,8 +1866,8 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres return result; } -/* New function to allow buffer re-using instead of allocating memory for the delta image every time */ -void Image::Delta( const Image &image, Image* targetimage) const { +/* New function to allow buffer re-using instead of allocationg memory for the delta image every time */ +void Image::Delta(const Image &image, Image* targetimage) const { #ifdef ZM_IMAGE_PROFILING struct timespec start,end,diff; unsigned long long executetime; @@ -1842,7 +1924,7 @@ void Image::Delta( const Image &image, Image* targetimage) const { #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); - timespec_diff(&start,&end,&diff); + TimespecDiff(&start,&end,&diff); executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)pixels)/((((long double)executetime)/1000)); @@ -1850,10 +1932,10 @@ void Image::Delta( const Image &image, Image* targetimage) const { #endif } -const Coord Image::centreCoord( const char *text ) const { +const Coord Image::centreCoord( const char *text, int size=1 ) const { int index = 0; int line_no = 0; - int text_len = strlen( text ); + int text_len = strlen(text); int line_len = 0; int max_line_len = 0; const char *line = text; @@ -1869,8 +1951,12 @@ const Coord Image::centreCoord( const char *text ) const { line = text+index; line_no++; } - int x = (width - (max_line_len * ZM_CHAR_WIDTH) ) / 2; - int y = (height - (line_no * LINE_HEIGHT) ) / 2; + + FontVariant const &font_variant = font.GetFontVariant(size - 1); + uint16_t char_width = font_variant.GetCharWidth(); + uint16_t char_height = font_variant.GetCharHeight(); + int x = (width - (max_line_len * char_width )) / 2; + int y = (height - (line_no * char_height) ) / 2; return Coord(x, y); } @@ -1916,179 +2002,135 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour } /* RGB32 compatible: complete */ -void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int size, const Rgb fg_colour, const Rgb bg_colour ) -{ - strncpy(text, p_text, sizeof(text)-1); +/* Bitmap decoding trick has been adopted from here: +https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/ +*/ +void Image::Annotate( + const std::string &text, + const Coord &coord, + const uint8 size, + const Rgb fg_colour, + const Rgb bg_colour) { + annotation_ = text; - unsigned int index = 0; - unsigned int line_no = 0; - unsigned int text_len = strlen(text); - unsigned int line_len = 0; - const char *line = text; + const Rgb fg_rgb_col = rgb_convert(fg_colour, subpixelorder); + const Rgb bg_rgb_col = rgb_convert(bg_colour, subpixelorder); - const uint8_t fg_r_col = RED_VAL_RGBA(fg_colour); - const uint8_t fg_g_col = GREEN_VAL_RGBA(fg_colour); - const uint8_t fg_b_col = BLUE_VAL_RGBA(fg_colour); - const uint8_t fg_bw_col = fg_colour & 0xff; - const Rgb fg_rgb_col = rgb_convert(fg_colour,subpixelorder); - const bool fg_trans = (fg_colour == RGB_TRANSPARENT); + FontVariant const &font_variant = font.GetFontVariant(size - 1); + const uint16 char_width = font_variant.GetCharWidth(); + const uint16 char_height = font_variant.GetCharHeight(); - const uint8_t bg_r_col = RED_VAL_RGBA(bg_colour); - const uint8_t bg_g_col = GREEN_VAL_RGBA(bg_colour); - const uint8_t bg_b_col = BLUE_VAL_RGBA(bg_colour); - const uint8_t bg_bw_col = bg_colour & 0xff; - const Rgb bg_rgb_col = rgb_convert(bg_colour,subpixelorder); - const bool bg_trans = (bg_colour == RGB_TRANSPARENT); + std::vector lines = Split(annotation_, '\n'); + std::size_t max_line_length = 0; + for (const std::string &s : lines) { + max_line_length = std::max(max_line_length, s.size()); + } - int zm_text_bitmask = 0x80; - if ( size == 2 ) - zm_text_bitmask = 0x8000; + uint32 x0_max = width - (max_line_length * char_width); + uint32 y0_max = height - (lines.size() * char_height); - while ( (index < text_len) && (line_len = strcspn(line, "\n")) ) { + // Calculate initial coordinates of annotation so that everything is displayed even if the + // user set coordinates would prevent that. + uint32 x0 = ZM::clamp(static_cast(coord.X()), 0u, x0_max); + uint32 y0 = ZM::clamp(static_cast(coord.Y()), 0u, y0_max); - unsigned int line_width = line_len * ZM_CHAR_WIDTH * size; + uint32 y = y0; + for (const std::string &line : lines) { + uint32 x = x0; - unsigned int lo_line_x = coord.X(); - unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size); - - unsigned int min_line_x = 0; - unsigned int max_line_x = width - line_width; - unsigned int min_line_y = 0; - unsigned int max_line_y = height - (LINE_HEIGHT * size); - - if ( lo_line_x > max_line_x ) - lo_line_x = max_line_x; - if ( lo_line_x < min_line_x ) - lo_line_x = min_line_x; - if ( lo_line_y > max_line_y ) - lo_line_y = max_line_y; - if ( lo_line_y < min_line_y ) - lo_line_y = min_line_y; - - unsigned int hi_line_x = lo_line_x + line_width; - unsigned int hi_line_y = lo_line_y + (LINE_HEIGHT * size); - - // Clip anything that runs off the right of the screen - if ( hi_line_x > width ) - hi_line_x = width; - if ( hi_line_y > height ) - hi_line_y = height; - - if ( colours == ZM_COLOUR_GRAY8 ) { - unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) { - unsigned char *temp_ptr = ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if ( size == 2 ) { - if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - } else { - if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + if (colours == ZM_COLOUR_GRAY8) { + uint8 *ptr = &buffer[(y * width) + x0]; + for (char c : line) { + for (uint64 cp_row : font_variant.GetCodepoint(c)) { + if (bg_colour != kRGBTransparent) { + std::fill(ptr, ptr + char_width, static_cast(bg_colour & 0xff)); } - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) - *temp_ptr = fg_bw_col; - } else if ( !bg_trans ) { - *temp_ptr = bg_bw_col; - } + + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + *(ptr + column_idx) = fg_colour & 0xff; + cp_row = cp_row & (cp_row - 1); } + ptr += width; + } + ptr -= (width * char_height); + ptr += char_width; + x += char_width; + if (x >= width) { + break; } } - } else if ( colours == ZM_COLOUR_RGB24 ) { - unsigned int wc = width * colours; + } else if (colours == ZM_COLOUR_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((y * width) + x0) * bytesPerPixel]; - unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { - unsigned char *temp_ptr = ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if ( size == 2 ) { - if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - } else { - if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; - } - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) { - RED_PTR_RGBA(temp_ptr) = fg_r_col; - GREEN_PTR_RGBA(temp_ptr) = fg_g_col; - BLUE_PTR_RGBA(temp_ptr) = fg_b_col; - } - } else if ( !bg_trans ) { - RED_PTR_RGBA(temp_ptr) = bg_r_col; - GREEN_PTR_RGBA(temp_ptr) = bg_g_col; - BLUE_PTR_RGBA(temp_ptr) = bg_b_col; + for (char c : line) { + for (uint64 cp_row : font_variant.GetCodepoint(c)) { + if (bg_colour != kRGBTransparent) { + for (uint16 i = 0; i < char_width; i++) { // We need to set individual r,g,b components + uint8 *colour_ptr = ptr + (i * bytesPerPixel); + RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(bg_colour); + GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(bg_colour); + BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(bg_colour); } } + + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + uint8 *colour_ptr = ptr + (column_idx * bytesPerPixel); + RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(fg_colour); + GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(fg_colour); + BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(fg_colour); + cp_row = cp_row & (cp_row - 1); + } + ptr += width * bytesPerPixel; + } + ptr -= (width * char_height * bytesPerPixel); + ptr += char_width * bytesPerPixel; + x += char_width; + if (x >= width) { + break; } } - } else if ( colours == ZM_COLOUR_RGB32 ) { - unsigned int wc = width * colours; + } else if (colours == ZM_COLOUR_RGB32) { + constexpr uint8 bytesPerPixel = 4; + Rgb *ptr = reinterpret_cast(&buffer[((y * width) + x0) * bytesPerPixel]); - uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { - Rgb* temp_ptr = (Rgb*)ptr; - for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { - int f; - if ( size == 2 ) { - if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; - } else { - if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for (char c : line) { + for (uint64 cp_row : font_variant.GetCodepoint(c)) { + if (bg_colour != kRGBTransparent) { + std::fill(ptr, ptr + char_width, bg_rgb_col); } - for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { - if ( f & (zm_text_bitmask >> i) ) { - if ( !fg_trans ) { - *temp_ptr = fg_rgb_col; - } - } else if ( !bg_trans ) { - *temp_ptr = bg_rgb_col; - } + + while (cp_row != 0) { + uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding(); + *(ptr + column_idx) = fg_rgb_col; + cp_row = cp_row & (cp_row - 1); } + ptr += width; + } + ptr -= (width * char_height); + ptr += char_width; + x += char_width; + if (x >= width) { + break; } } - } else { Error("Annotate called with unexpected colours: %d", colours); return; } - - index += line_len; - while ( text[index] == '\n' ) { - index++; + y += char_height; + if (y >= height) { + break; } - line = text+index; - line_no++; } } void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) { char time_text[64]; - strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime(&when)); + tm when_tm = {}; + strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when, &when_tm)); if ( label ) { // Assume label is max 64, + ' - ' + 64 chars of time_text char text[132]; @@ -2449,7 +2491,7 @@ void Image::Fill(Rgb colour, int density, const Polygon &polygon) { global_edges[n_global_edges]._1_m = dx/dy; n_global_edges++; } - qsort( global_edges, n_global_edges, sizeof(*global_edges), Edge::CompareYX ); + std::sort(global_edges, global_edges + n_global_edges, Edge::CompareYX); #ifndef ZM_DBG_OFF if ( logLevel() >= Logger::DEBUG9 ) { @@ -2478,7 +2520,7 @@ void Image::Fill(Rgb colour, int density, const Polygon &polygon) { break; } } - qsort(active_edges, n_active_edges, sizeof(*active_edges), Edge::CompareX); + std::sort(active_edges, active_edges + n_active_edges, Edge::CompareX); #ifndef ZM_DBG_OFF if ( logLevel() >= Logger::DEBUG9 ) { for ( int i = 0; i < n_active_edges; i++ ) { @@ -2763,11 +2805,11 @@ void Image::Scale(unsigned int factor) { h_count += factor; h_index = h_count/ZM_SCALE_BASE; for ( unsigned int f = last_h_index+1; f < h_index; f++ ) { - memcpy( pd, pd-nwc, nwc ); + memcpy(pd, pd-nwc, nwc); pd += nwc; } last_h_index = h_index; - } + } // end foreach line new_width = last_w_index; new_height = last_h_index; } else { @@ -4680,8 +4722,8 @@ void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned lon /* YUYV to RGB24 - relocated from zm_local_camera.cpp */ __attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result, unsigned long count) { - unsigned int r,g,b; - unsigned int y1,y2,u,v; + int32 r,g,b; + int32 y1,y2,u,v; for(unsigned int i=0; i < count; i += 2, col1 += 4, result += 6) { y1 = col1[0]; u = col1[1]; @@ -5230,3 +5272,5 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_ pncurrent += 4; } } + + diff --git a/src/zm_image.h b/src/zm_image.h index 8f86c3e30..671cc0487 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -20,26 +20,21 @@ #ifndef ZM_IMAGE_H #define ZM_IMAGE_H -#include "zm.h" -extern "C" { -#include "zm_jpeg.h" -} -#include "zm_rgb.h" #include "zm_coord.h" -#include "zm_box.h" -#include "zm_poly.h" -#include "zm_mem_utils.h" -#include "zm_utils.h" - -class Image; #include "zm_ffmpeg.h" - -#include +#include "zm_jpeg.h" +#include "zm_logger.h" +#include "zm_mem_utils.h" +#include "zm_rgb.h" #if HAVE_ZLIB_H #include #endif // HAVE_ZLIB_H +class Box; +class Image; +class Polygon; + #define ZM_BUFTYPE_DONTFREE 0 #define ZM_BUFTYPE_MALLOC 1 #define ZM_BUFTYPE_NEW 2 @@ -64,7 +59,7 @@ inline static uint8_t* AllocBuffer(size_t p_bufsize) { } inline static void DumpBuffer(uint8_t* buffer, int buffertype) { - if ( buffer && buffertype != ZM_BUFTYPE_DONTFREE ) { + if ( buffer && (buffertype != ZM_BUFTYPE_DONTFREE) ) { if ( buffertype == ZM_BUFTYPE_ZM ) { zm_freealigned(buffer); } else if ( buffertype == ZM_BUFTYPE_MALLOC ) { @@ -99,196 +94,221 @@ class Image { blend_fptr_t blend; void update_function_pointers(); -protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; + protected: + struct Edge { + int min_y; + int max_y; + double min_x; + double _1_m; - static int CompareYX( const void *p1, const void *p2 ) { - const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); - if ( e1->min_y == e2->min_y ) - return( int(e1->min_x - e2->min_x) ); - else - return( int(e1->min_y - e2->min_y) ); - } - static int CompareX( const void *p1, const void *p2 ) { - const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); - return( int(e1->min_x - e2->min_x) ); - } - }; + static bool CompareYX(const Edge &e1, const Edge &e2) { + if ( e1.min_y == e2.min_y ) + return e1.min_x < e2.min_x; + return e1.min_y < e2.min_y; + } + + static bool CompareX(const Edge &e1, const Edge &e2) { + return e1.min_x < e2.min_x; + } + }; - inline void DumpImgBuffer() { - DumpBuffer(buffer, buffertype); - buffer = nullptr; - allocation = 0; - } - - inline void AllocImgBuffer(size_t p_bufsize) { - if ( buffer ) - DumpImgBuffer(); - - buffer = AllocBuffer(p_bufsize); - buffertype = ZM_BUFTYPE_ZM; - allocation = p_bufsize; - } -public: - enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; - enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; + inline void AllocImgBuffer(size_t p_bufsize) { + if ( buffer ) + DumpImgBuffer(); -protected: - static bool initialised; - static unsigned char *abs_table; - static unsigned char *y_r_table; - static unsigned char *y_g_table; - static unsigned char *y_b_table; - static jpeg_compress_struct *writejpg_ccinfo[101]; - static jpeg_compress_struct *encodejpg_ccinfo[101]; - static jpeg_decompress_struct *readjpg_dcinfo; - static jpeg_decompress_struct *decodejpg_dcinfo; - static struct zm_error_mgr jpg_err; + buffer = AllocBuffer(p_bufsize); + buffertype = ZM_BUFTYPE_ZM; + allocation = p_bufsize; + } - unsigned int width; - unsigned int linesize; - unsigned int height; - unsigned int pixels; - unsigned int colours; - unsigned int padding; - unsigned int size; - unsigned int subpixelorder; - unsigned long allocation; - uint8_t *buffer; - int buffertype; /* 0=not ours, no need to call free(), 1=malloc() buffer, 2=new buffer */ - int holdbuffer; /* Hold the buffer instead of replacing it with new one */ - char text[1024]; + public: + enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; + enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; -public: - Image(); - explicit Image(const char *filename); - Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); - Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); - explicit Image( const Image &p_image ); - explicit Image( const AVFrame *frame ); + protected: + static bool initialised; + static unsigned char *abs_table; + static unsigned char *y_r_table; + static unsigned char *y_g_table; + static unsigned char *y_b_table; + static jpeg_compress_struct *writejpg_ccinfo[101]; + static jpeg_compress_struct *encodejpg_ccinfo[101]; + static jpeg_decompress_struct *readjpg_dcinfo; + static jpeg_decompress_struct *decodejpg_dcinfo; + static struct zm_error_mgr jpg_err; - ~Image(); - static void Initialise(); - static void Deinitialise(); + unsigned int width; + unsigned int linesize; + unsigned int height; + unsigned int pixels; + unsigned int colours; + unsigned int padding; + unsigned int size; + unsigned int subpixelorder; + unsigned long allocation; + _AVPIXELFORMAT imagePixFormat; + uint8_t *buffer; + int buffertype; /* 0=not ours, no need to call free(), 1=malloc() buffer, 2=new buffer */ + int holdbuffer; /* Hold the buffer instead of replacing it with new one */ + std::string annotation_; - inline unsigned int Width() const { return width; } - inline unsigned int LineSize() const { return linesize; } - inline unsigned int Height() const { return height; } - inline unsigned int Pixels() const { return pixels; } - inline unsigned int Colours() const { return colours; } - inline unsigned int SubpixelOrder() const { return subpixelorder; } - inline unsigned int Size() const { return size; } - - /* Internal buffer should not be modified from functions outside of this class */ - inline const uint8_t* Buffer() const { return buffer; } - inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return &buffer[(y*linesize)+x]; } - /* Request writeable buffer */ - uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); - - inline int IsBufferHeld() const { return holdbuffer; } - inline void HoldBuffer(int tohold) { holdbuffer = tohold; } - - inline void Empty() { - if ( !holdbuffer ) - DumpImgBuffer(); + public: + Image(); + explicit Image(const char *filename); + Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); + Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0, unsigned int padding=0); + explicit Image(const Image &p_image); + explicit Image(const AVFrame *frame); - width = linesize = height = colours = size = pixels = subpixelorder = 0; - } - - void Assign( - unsigned int p_width, - unsigned int p_height, - unsigned int p_colours, - unsigned int p_subpixelorder, - const uint8_t* new_buffer, - const size_t buffer_size); - void Assign(const Image &image); - void AssignDirect( - const unsigned int p_width, - const unsigned int p_height, - const unsigned int p_colours, - const unsigned int p_subpixelorder, - uint8_t *new_buffer, - const size_t buffer_size, - const int p_buffertype); + ~Image(); + static void Initialise(); + static void Deinitialise(); - inline void CopyBuffer(const Image &image) { - Assign(image); - } - inline Image &operator=(const Image &image) { - Assign(image); - return *this; - } - inline Image &operator=(const unsigned char *new_buffer) { - (*fptr_imgbufcpy)(buffer, new_buffer, size); - return *this; - } + inline void DumpImgBuffer() { + DumpBuffer(buffer, buffertype); + buffertype = ZM_BUFTYPE_DONTFREE; + buffer = nullptr; + allocation = 0; + } + inline unsigned int Width() const { return width; } + inline unsigned int LineSize() const { return linesize; } + inline unsigned int Height() const { return height; } + inline unsigned int Pixels() const { return pixels; } + inline unsigned int Colours() const { return colours; } + inline unsigned int SubpixelOrder() const { return subpixelorder; } + inline unsigned int Size() const { return size; } - bool ReadRaw( const char *filename ); - bool WriteRaw( const char *filename ) const; + inline AVPixelFormat AVPixFormat() { + if ( colours == ZM_COLOUR_RGB32 ) { + return AV_PIX_FMT_RGBA; + } else if ( colours == ZM_COLOUR_RGB24 ) { + if ( subpixelorder == ZM_SUBPIX_ORDER_BGR){ + return AV_PIX_FMT_BGR24; + } else { + return AV_PIX_FMT_RGB24; + } + } else if ( colours == ZM_COLOUR_GRAY8 ) { + return AV_PIX_FMT_GRAY8; + } else { + Error("Unknown colours (%d)",colours); + return AV_PIX_FMT_RGBA; + } + } - bool ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); + /* Internal buffer should not be modified from functions outside of this class */ + inline const uint8_t* Buffer() const { return buffer; } + inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize)+x]; } + /* Request writeable buffer */ + uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); + // Is only acceptable on a pre-allocated buffer + uint8_t* WriteBuffer() { return holdbuffer ? buffer : nullptr; }; - bool WriteJpeg ( const char *filename) const; - bool WriteJpeg ( const char *filename, bool on_blocking_abort) const; - bool WriteJpeg ( const char *filename, int quality_override ) const; - bool WriteJpeg ( const char *filename, struct timeval timestamp ) const; - bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp ) const; - bool WriteJpeg ( const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort ) const; - + inline int IsBufferHeld() const { return holdbuffer; } + inline void HoldBuffer(int tohold) { holdbuffer = tohold; } - bool DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); - bool EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_override=0 ) const; + inline void Empty() { + if ( !holdbuffer ) + DumpImgBuffer(); + + width = linesize = height = colours = size = pixels = subpixelorder = 0; + } + + void Assign( + unsigned int p_width, + unsigned int p_height, + unsigned int p_colours, + unsigned int p_subpixelorder, + const uint8_t* new_buffer, + const size_t buffer_size); + void Assign(const Image &image); + bool Assign(const AVFrame *frame); + bool Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame); + void AssignDirect( + const unsigned int p_width, + const unsigned int p_height, + const unsigned int p_colours, + const unsigned int p_subpixelorder, + uint8_t *new_buffer, + const size_t buffer_size, + const int p_buffertype); + + int PopulateFrame(AVFrame *frame); + + inline void CopyBuffer(const Image &image) { + Assign(image); + } + inline Image &operator=(const Image &image) { + Assign(image); + return *this; + } + inline Image &operator=(const unsigned char *new_buffer) { + (*fptr_imgbufcpy)(buffer, new_buffer, size); + return *this; + } + + bool ReadRaw(const char *filename); + bool WriteRaw(const char *filename) const; + + bool ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); + + bool WriteJpeg(const char *filename) const; + bool WriteJpeg(const char *filename, bool on_blocking_abort) const; + bool WriteJpeg(const char *filename, int quality_override) const; + bool WriteJpeg(const char *filename, struct timeval timestamp) const; + bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const; + bool WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const; + + bool DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder); + bool EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_override=0) const; #if HAVE_ZLIB_H - bool Unzip( const Bytef *inbuffer, unsigned long inbuffer_size ); - bool Zip( Bytef *outbuffer, unsigned long *outbuffer_size, int compression_level=Z_BEST_SPEED ) const; + bool Unzip(const Bytef *inbuffer, unsigned long inbuffer_size); + bool Zip(Bytef *outbuffer, unsigned long *outbuffer_size, int compression_level=Z_BEST_SPEED) const; #endif // HAVE_ZLIB_H - bool Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsigned int hi_y ); - bool Crop( const Box &limits ); + bool Crop(unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsigned int hi_y); + bool Crop(const Box &limits); - void Overlay( const Image &image ); - void Overlay( const Image &image, unsigned int x, unsigned int y ); - void Blend( const Image &image, int transparency=12 ); - static Image *Merge( unsigned int n_images, Image *images[] ); - static Image *Merge( unsigned int n_images, Image *images[], double weight ); - static Image *Highlight( unsigned int n_images, Image *images[], const Rgb threshold=RGB_BLACK, const Rgb ref_colour=RGB_RED ); - //Image *Delta( const Image &image ) const; - void Delta( const Image &image, Image* targetimage) const; + void Overlay(const Image &image); + void Overlay(const Image &image, unsigned int x, unsigned int y); + void Blend(const Image &image, int transparency=12); + static Image *Merge( unsigned int n_images, Image *images[] ); + static Image *Merge( unsigned int n_images, Image *images[], double weight ); + static Image *Highlight(unsigned int n_images, Image *images[], Rgb threshold = kRGBBlack, Rgb ref_colour = kRGBRed); + //Image *Delta( const Image &image ) const; + void Delta( const Image &image, Image* targetimage) const; - const Coord centreCoord( const char *text ) const; - void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); - void Annotate( const char *p_text, const Coord &coord, const unsigned int size=1, const Rgb fg_colour=RGB_WHITE, const Rgb bg_colour=RGB_BLACK ); - Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 ); - //Image *HighlightEdges( Rgb colour, const Polygon &polygon ); - void Timestamp( const char *label, const time_t when, const Coord &coord, const int size ); - void Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder); - void DeColourise(); + const Coord centreCoord(const char *text, const int size) const; + void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); + void Annotate(const std::string &text, + const Coord &coord, + uint8 size = 1, + Rgb fg_colour = kRGBWhite, + Rgb bg_colour = kRGBBlack); + Image *HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits=0 ); + //Image *HighlightEdges( Rgb colour, const Polygon &polygon ); + void Timestamp( const char *label, const time_t when, const Coord &coord, const int size ); + void Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder); + void DeColourise(); - void Clear() { memset( buffer, 0, size ); } - void Fill( Rgb colour, const Box *limits=0 ); - void Fill( Rgb colour, int density, const Box *limits=0 ); - void Outline( Rgb colour, const Polygon &polygon ); - void Fill( Rgb colour, const Polygon &polygon ); - void Fill( Rgb colour, int density, const Polygon &polygon ); + void Clear() { memset( buffer, 0, size ); } + void Fill( Rgb colour, const Box *limits=0 ); + void Fill( Rgb colour, int density, const Box *limits=0 ); + void Outline( Rgb colour, const Polygon &polygon ); + void Fill( Rgb colour, const Polygon &polygon ); + void Fill( Rgb colour, int density, const Polygon &polygon ); - void Rotate( int angle ); - void Flip( bool leftright ); - void Scale( unsigned int factor ); + void Rotate( int angle ); + void Flip( bool leftright ); + void Scale( unsigned int factor ); - void Deinterlace_Discard(); - void Deinterlace_Linear(); - void Deinterlace_Blend(); - void Deinterlace_Blend_CustomRatio(int divider); - void Deinterlace_4Field(const Image* next_image, unsigned int threshold); - + void Deinterlace_Discard(); + void Deinterlace_Linear(); + void Deinterlace_Blend(); + void Deinterlace_Blend_CustomRatio(int divider); + void Deinterlace_4Field(const Image* next_image, unsigned int threshold); }; #endif // ZM_IMAGE_H diff --git a/src/zm_jpeg.cpp b/src/zm_jpeg.cpp index 9865d4db4..c3fddec1f 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -18,76 +18,66 @@ */ #include "zm_jpeg.h" + #include "zm_logger.h" -#include - /* Overridden error handlers, mostly for decompression */ -extern "C" -{ +extern "C" { #define MAX_JPEG_ERRS 25 static int jpeg_err_count = 0; -void zm_jpeg_error_silent( j_common_ptr cinfo ){ +void zm_jpeg_error_silent(j_common_ptr cinfo) { zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; - longjmp( zmerr->setjmp_buffer, 1 ); -} -void zm_jpeg_emit_silence( j_common_ptr cinfo, int msg_level ){ + longjmp(zmerr->setjmp_buffer, 1); } -void zm_jpeg_error_exit( j_common_ptr cinfo ) -{ +void zm_jpeg_emit_silence(j_common_ptr cinfo, int msg_level) { +} + +void zm_jpeg_error_exit(j_common_ptr cinfo) { static char buffer[JMSG_LENGTH_MAX]; zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; - (zmerr->pub.format_message)( cinfo, buffer ); + (zmerr->pub.format_message)(cinfo, buffer); - Error( "%s", buffer ); - if ( ++jpeg_err_count == MAX_JPEG_ERRS ) - { - Fatal( "Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count ); + Error("%s", buffer); + if ( ++jpeg_err_count == MAX_JPEG_ERRS ) { + Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count); } - longjmp( zmerr->setjmp_buffer, 1 ); + longjmp(zmerr->setjmp_buffer, 1); } -void zm_jpeg_emit_message( j_common_ptr cinfo, int msg_level ) -{ +void zm_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { static char buffer[JMSG_LENGTH_MAX]; zm_error_ptr zmerr = (zm_error_ptr)cinfo->err; - if ( msg_level < 0 ) - { + if ( msg_level < 0 ) { /* It's a warning message. Since corrupt files may generate many warnings, * the policy implemented here is to show only the first warning, * unless trace_level >= 3. */ - if ( zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3 ) - { - (zmerr->pub.format_message)( cinfo, buffer ); - if (!strstr(buffer, "Corrupt JPEG data:")) - Warning( "%s", buffer ); + if ( zmerr->pub.num_warnings == 0 || zmerr->pub.trace_level >= 3 ) { + (zmerr->pub.format_message)(cinfo, buffer); + if ( !strstr(buffer, "Corrupt JPEG data:") ) + Warning("%s", buffer); } /* Always count warnings in num_warnings. */ zmerr->pub.num_warnings++; - } - else - { + } else { /* It's a trace message. Show it if trace_level >= msg_level. */ - if ( zmerr->pub.trace_level >= msg_level ) - { - (zmerr->pub.format_message)( cinfo, buffer ); - Debug( msg_level, "%s", buffer ); + if ( zmerr->pub.trace_level >= msg_level ) { + (zmerr->pub.format_message)(cinfo, buffer); + Debug(msg_level, "%s", buffer); } } } /* Expanded data destination object for memory */ -typedef struct -{ +typedef struct { struct jpeg_destination_mgr pub; /* public fields */ JOCTET *outbuffer; /* target buffer */ @@ -104,8 +94,7 @@ typedef mem_destination_mgr * mem_dest_ptr; * before any data is actually written. */ -static void init_destination (j_compress_ptr cinfo) -{ +static void init_destination (j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; /* Allocate the output buffer --- it will be released when done with image */ @@ -117,7 +106,6 @@ static void init_destination (j_compress_ptr cinfo) *(dest->outbuffer_size) = 0; } - /* * Empty the output buffer --- called whenever buffer fills up. * @@ -141,17 +129,16 @@ static void init_destination (j_compress_ptr cinfo) * write it out when emptying the buffer externally. */ -static boolean empty_output_buffer (j_compress_ptr cinfo) -{ +static boolean empty_output_buffer(j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; - memcpy( dest->outbuffer+*(dest->outbuffer_size), dest->buffer, OUTPUT_BUF_SIZE ); + memcpy(dest->outbuffer+*(dest->outbuffer_size), dest->buffer, OUTPUT_BUF_SIZE); *(dest->outbuffer_size) += OUTPUT_BUF_SIZE; dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; - return( TRUE ); + return TRUE; } /* @@ -163,14 +150,12 @@ static boolean empty_output_buffer (j_compress_ptr cinfo) * for error exit. */ -static void term_destination (j_compress_ptr cinfo) -{ +static void term_destination(j_compress_ptr cinfo) { mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest; size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; - if ( datacount > 0 ) - { - memcpy( dest->outbuffer+*(dest->outbuffer_size), dest->buffer, datacount ); + if ( datacount > 0 ) { + memcpy(dest->outbuffer+*(dest->outbuffer_size), dest->buffer, datacount); *(dest->outbuffer_size) += datacount; } } @@ -182,8 +167,7 @@ static void term_destination (j_compress_ptr cinfo) * for closing it after finishing compression. */ -void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_size ) -{ +void zm_jpeg_mem_dest(j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_size) { mem_dest_ptr dest; /* The destination object is made permanent so that multiple JPEG images @@ -192,10 +176,10 @@ void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_s * manager serially with the same JPEG object, because their private object * sizes may be different. Caveat programmer. */ - if ( cinfo->dest == nullptr ) - { + if ( cinfo->dest == nullptr ) { /* first time for this JPEG object? */ - cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_destination_mgr)); + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_destination_mgr)); } dest = (mem_dest_ptr) cinfo->dest; @@ -208,8 +192,7 @@ void zm_jpeg_mem_dest (j_compress_ptr cinfo, JOCTET *outbuffer, int *outbuffer_s /* Expanded data source object for memory input */ -typedef struct -{ +typedef struct { struct jpeg_source_mgr pub; /* public fields */ JOCTET * inbuffer; /* source stream */ @@ -229,8 +212,7 @@ typedef mem_source_mgr * mem_src_ptr; * before any data is actually read. */ -static void init_source (j_decompress_ptr cinfo) -{ +static void init_source (j_decompress_ptr cinfo) { mem_src_ptr src = (mem_src_ptr) cinfo->src; /* We reset the empty-input-file flag for each image, @@ -275,12 +257,11 @@ static void init_source (j_decompress_ptr cinfo) * the front of the buffer rather than discarding it. */ -static boolean fill_input_buffer (j_decompress_ptr cinfo) -{ +static boolean fill_input_buffer (j_decompress_ptr cinfo) { mem_src_ptr src = (mem_src_ptr) cinfo->src; size_t nbytes; - memcpy( src->buffer, src->inbuffer, (size_t) src->inbuffer_size ); + memcpy(src->buffer, src->inbuffer, (size_t) src->inbuffer_size); nbytes = src->inbuffer_size; if ( nbytes == 0 ) { @@ -297,10 +278,9 @@ static boolean fill_input_buffer (j_decompress_ptr cinfo) src->pub.bytes_in_buffer = nbytes; src->start_of_data = FALSE; - return( TRUE ); + return TRUE; } - /* * Skip data --- used to skip over a potentially large amount of * uninteresting data (such as an APPn marker). @@ -313,18 +293,15 @@ static boolean fill_input_buffer (j_decompress_ptr cinfo) * buffer is the application writer's problem. */ -static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) -{ +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { mem_src_ptr src = (mem_src_ptr) cinfo->src; /* Just a dumb implementation for now. Could use fseek() except * it doesn't work on pipes. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ - if ( num_bytes > 0 ) - { - while ( num_bytes > (long) src->pub.bytes_in_buffer ) - { + if ( num_bytes > 0 ) { + while ( num_bytes > (long) src->pub.bytes_in_buffer ) { num_bytes -= (long) src->pub.bytes_in_buffer; (void) fill_input_buffer(cinfo); /* note we assume that fill_input_buffer will never return FALSE, @@ -336,7 +313,6 @@ static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) } } - /* * Terminate source --- called by jpeg_finish_decompress * after all data has been read. Often a no-op. @@ -346,20 +322,17 @@ static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) * for error exit. */ -static void term_source (j_decompress_ptr cinfo) -{ +static void term_source(j_decompress_ptr cinfo) { /* no work necessary here */ } - /* * Prepare for input from a memory stream. * The caller must have already opened the stream, and is responsible * for closing it after finishing decompression. */ -void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuffer_size ) -{ +void zm_jpeg_mem_src(j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuffer_size) { mem_src_ptr src; /* The source object and input buffer are made permanent so that a series @@ -369,19 +342,15 @@ void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuff * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ - if ( cinfo->src == nullptr ) - { + if ( cinfo->src == nullptr ) { /* first time for this JPEG object? */ cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(mem_source_mgr)); src = (mem_src_ptr) cinfo->src; src->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, inbuffer_size * SIZEOF(JOCTET)); src->inbuffer_size_hwm = inbuffer_size; - } - else - { + } else { src = (mem_src_ptr) cinfo->src; - if ( src->inbuffer_size_hwm < inbuffer_size ) - { + if ( src->inbuffer_size_hwm < inbuffer_size ) { src->buffer = (JOCTET *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, inbuffer_size * SIZEOF(JOCTET)); src->inbuffer_size_hwm = inbuffer_size; } @@ -399,7 +368,7 @@ void zm_jpeg_mem_src( j_decompress_ptr cinfo, const JOCTET *inbuffer, int inbuff src->pub.next_input_byte = nullptr; /* until buffer loaded */ } -void zm_use_std_huff_tables( j_decompress_ptr cinfo ) { +void zm_use_std_huff_tables(j_decompress_ptr cinfo) { /* JPEG standard Huffman tables (cf. JPEG standard section K.3) */ /* IMPORTANT: these are only valid for 8-bit data precision! */ static const JHUFF_TBL dclumin = { @@ -467,7 +436,6 @@ void zm_use_std_huff_tables( j_decompress_ptr cinfo ) { cinfo->dc_huff_tbl_ptrs[1] = (JHUFF_TBL*)&dcchrome; cinfo->ac_huff_tbl_ptrs[0] = (JHUFF_TBL*)&aclumin; cinfo->ac_huff_tbl_ptrs[1] = (JHUFF_TBL*)&acchrome; - } } diff --git a/src/zm_jpeg.h b/src/zm_jpeg.h index f0b4a355c..acf47f809 100644 --- a/src/zm_jpeg.h +++ b/src/zm_jpeg.h @@ -15,13 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include +*/ +#include "jerror.h" #include "jinclude.h" #include "jpeglib.h" -#include "jerror.h" +#include // Stop complaints about deuplicate definitions #undef HAVE_STDLIB_H diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 2ac76d198..59cdc4ccf 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -15,13 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ -#include -#include "zm.h" -#include "zm_signal.h" #include "zm_libvlc_camera.h" +#include "zm_packet.h" +#include "zm_signal.h" +#include "zm_utils.h" +#include + #if HAVE_LIBVLC static void *libvlc_lib = nullptr; static void (*libvlc_media_player_release_f)(libvlc_media_player_t* ) = nullptr; @@ -92,12 +94,16 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) if ( newFrame || difftime(now, data->prevTime) >= 0.8 ) { data->prevTime = now; - data->newImage.updateValueSignal(true); + { + std::lock_guard lck(data->newImageMutex); + data->newImage = true; + } + data->newImageCv.notify_all(); } } LibvlcCamera::LibvlcCamera( - int p_id, + const Monitor *monitor, const std::string &p_path, const std::string &p_method, const std::string &p_options, @@ -112,7 +118,7 @@ LibvlcCamera::LibvlcCamera( bool p_record_audio ) : Camera( - p_id, + monitor, LIBVLC_SRC, p_width, p_height, @@ -163,6 +169,9 @@ LibvlcCamera::~LibvlcCamera() { if ( capture ) { Terminate(); } + + mLibvlcData.newImageCv.notify_all(); // to unblock on termination (zm_terminate) + if ( mLibvlcMediaPlayer != nullptr ) { (*libvlc_media_player_release_f)(mLibvlcMediaPlayer); mLibvlcMediaPlayer = nullptr; @@ -175,6 +184,10 @@ LibvlcCamera::~LibvlcCamera() { (*libvlc_release_f)(mLibvlcInstance); mLibvlcInstance = nullptr; } + if (libvlc_lib) { + dlclose(libvlc_lib); + libvlc_lib = nullptr; + } if ( mOptArgV != nullptr ) { delete[] mOptArgV; } @@ -200,7 +213,7 @@ void LibvlcCamera::Terminate() { int LibvlcCamera::PrimeCapture() { Debug(1, "Priming capture from %s, libvlc version %s", mPath.c_str(), (*libvlc_get_version_f)()); - StringVector opVect = split(Options(), ","); + StringVector opVect = Split(Options(), ","); // Set transport method as specified by method field, rtpUni is default if ( Method() == "rtpMulti" ) @@ -214,11 +227,11 @@ int LibvlcCamera::PrimeCapture() { if ( opVect.size() > 0 ) { mOptArgV = new char*[opVect.size()]; - Debug(2, "Number of Options: %d",opVect.size()); + Debug(2, "Number of Options: %zu", opVect.size()); for (size_t i=0; i< opVect.size(); i++) { - opVect[i] = trimSpaces(opVect[i]); + opVect[i] = TrimSpaces(opVect[i]); mOptArgV[i] = (char *)opVect[i].c_str(); - Debug(2, "set option %d to '%s'", i, opVect[i].c_str()); + Debug(2, "set option %zu to '%s'", i, opVect[i].c_str()); } } @@ -249,8 +262,8 @@ int LibvlcCamera::PrimeCapture() { // Libvlc wants 32 byte alignment for images (should in theory do this for all image lines) mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); - - mLibvlcData.newImage.setValueImmediate(false); + + mLibvlcData.newImage = false; (*libvlc_media_player_play_f)(mLibvlcMediaPlayer); @@ -263,27 +276,26 @@ int LibvlcCamera::PreCapture() { } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::Capture(Image &image) { - +int LibvlcCamera::Capture( ZMPacket &zm_packet ) { // newImage is a mutex/condition based flag to tell us when there is an image available - while( !mLibvlcData.newImage.getValueImmediate() ) { - if (zm_terminate) - return 0; - mLibvlcData.newImage.getUpdatedValue(1); + { + std::unique_lock lck(mLibvlcData.newImageMutex); + mLibvlcData.newImageCv.wait(lck, [&]{ return mLibvlcData.newImage || zm_terminate; }); + mLibvlcData.newImage = false; } + if (zm_terminate) + return 0; + mLibvlcData.mutex.lock(); - image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); - mLibvlcData.newImage.setValueImmediate(false); + zm_packet.image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + zm_packet.packet.stream_index = mVideoStreamId; + zm_packet.stream = mVideoStream; mLibvlcData.mutex.unlock(); return 1; } -int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) { - return 0; -} - int LibvlcCamera::PostCapture() { return 0; } @@ -307,9 +319,9 @@ void LibvlcCamera::log_callback(void *ptr, int level, const libvlc_log_t *ctx, c } if ( log ) { - char logString[8192]; - vsnprintf(logString, sizeof(logString)-1, fmt, vargs); - log->logPrint(false, __FILE__, __LINE__, log_level, logString); + char logString[8192]; + vsnprintf(logString, sizeof(logString) - 1, fmt, vargs); + log->logPrint(false, __FILE__, __LINE__, log_level, "%s", logString); } } #endif // HAVE_LIBVLC diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 038d30f05..7d9be27c1 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -20,9 +20,9 @@ #ifndef ZM_LIBVLC_CAMERA_H #define ZM_LIBVLC_CAMERA_H -#include "zm_buffer.h" #include "zm_camera.h" -#include "zm_thread.h" +#include +#include #if HAVE_LIBVLC @@ -31,14 +31,16 @@ #endif // Used by libvlc callbacks -struct LibvlcPrivateData -{ +struct LibvlcPrivateData { uint8_t* buffer; uint8_t* prevBuffer; time_t prevTime; uint32_t bufferSize; - Mutex mutex; - ThreadData newImage; + std::mutex mutex; + + bool newImage; + std::mutex newImageMutex; + std::condition_variable newImageCv; }; class LibvlcCamera : public Camera { @@ -58,7 +60,7 @@ protected: libvlc_media_player_t *mLibvlcMediaPlayer; public: - LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + LibvlcCamera( const Monitor *monitor, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~LibvlcCamera(); const std::string &Path() const { return mPath; } @@ -68,12 +70,11 @@ public: void Initialise(); void Terminate(); - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); - int PostCapture(); - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; + int Close() override { return 0; }; }; #endif // HAVE_LIBVLC diff --git a/src/zm_libvnc_camera.cpp b/src/zm_libvnc_camera.cpp index 315cc19dd..faa594a7e 100644 --- a/src/zm_libvnc_camera.cpp +++ b/src/zm_libvnc_camera.cpp @@ -1,8 +1,7 @@ -#include -#include "zm.h" -#include "zm_signal.h" #include "zm_libvnc_camera.h" -#include "zm_swscale.h" + +#include "zm_packet.h" +#include #if HAVE_LIBVNC @@ -19,11 +18,11 @@ static int (*WaitForMessage_f)(rfbClient*, unsigned int) = nullptr; static rfbBool (*HandleRFBServerMessage_f)(rfbClient*) = nullptr; void bind_libvnc_symbols() { - if ( libvnc_lib != nullptr ) // Safe-check + if (libvnc_lib != nullptr) // Safe-check return; libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL); - if ( !libvnc_lib ) { + if (!libvnc_lib) { Error("Error loading libvncclient: %s", dlerror()); return; } @@ -37,29 +36,50 @@ void bind_libvnc_symbols() { *(void**) (&HandleRFBServerMessage_f) = dlsym(libvnc_lib, "HandleRFBServerMessage"); } -static void GotFrameBufferUpdateCallback(rfbClient *rfb, int x, int y, int w, int h){ +static void GotFrameBufferUpdateCallback(rfbClient *rfb, int x, int y, int w, int h) { VncPrivateData *data = (VncPrivateData *)(*rfbClientGetClientData_f)(rfb, &TAG_0); data->buffer = rfb->frameBuffer; + Debug(1, "GotFrameBufferUpdateallback x:%d y:%d w%d h:%d width: %d, height: %d, buffer %p", + x,y,w,h, rfb->width, rfb->height, rfb->frameBuffer); } -static char* GetPasswordCallback(rfbClient* cl){ +static char* GetPasswordCallback(rfbClient* cl) { + Debug(1, "Getcredentials: %s", static_cast((*rfbClientGetClientData_f)(cl, &TAG_1))); return strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_1)); } static rfbCredential* GetCredentialsCallback(rfbClient* cl, int credentialType){ - rfbCredential *c = (rfbCredential *)malloc(sizeof(rfbCredential)); - if ( credentialType != rfbCredentialTypeUser ) { - free(c); + if (credentialType != rfbCredentialTypeUser) { + Debug(1, "credentialType != rfbCredentialTypeUser"); return nullptr; } + rfbCredential *c = (rfbCredential *)malloc(sizeof(rfbCredential)); + Debug(1, "Getcredentials: %s:%s", + static_cast((*rfbClientGetClientData_f)(cl, &TAG_1)), + static_cast((*rfbClientGetClientData_f)(cl, &TAG_2))); c->userCredential.password = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_1)); c->userCredential.username = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_2)); return c; } +static rfbBool resize(rfbClient* client) { + if (client->frameBuffer) { + Debug(1, "Freeing old frame buffer"); + av_free(client->frameBuffer); + } + + int bufferSize = 4*client->width*client->height; + // libVNC doesn't do alignment or padding in each line + //SWScale::GetBufferSize(AV_PIX_FMT_RGBA, client->width, client->height); + client->frameBuffer = (uint8_t *)av_malloc(bufferSize); + Debug(1, "Allocing new frame buffer %dx%d = %d", client->width, client->height, bufferSize); + + return TRUE; +} + VncCamera::VncCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &host, const std::string &port, const std::string &user, @@ -74,7 +94,7 @@ VncCamera::VncCamera( bool p_capture, bool p_record_audio ) : Camera( - p_monitor_id, + monitor, VNC_SRC, p_width, p_height, @@ -87,111 +107,152 @@ VncCamera::VncCamera( p_capture, p_record_audio ), + mRfb(nullptr), + mVncData({}), mHost(host), mPort(port), mUser(user), mPass(pass) { - Debug(2, "Host:%s Port: %s User: %s Pass:%s", mHost.c_str(), mPort.c_str(), mUser.c_str(), mPass.c_str()); - - if ( colours == ZM_COLOUR_RGB32 ) { + if (colours == ZM_COLOUR_RGB32) { subpixelorder = ZM_SUBPIX_ORDER_RGBA; mImgPixFmt = AV_PIX_FMT_RGBA; - mBpp = 4; - } else if ( colours == ZM_COLOUR_RGB24 ) { + } else if (colours == ZM_COLOUR_RGB24) { subpixelorder = ZM_SUBPIX_ORDER_RGB; mImgPixFmt = AV_PIX_FMT_RGB24; - mBpp = 3; - } else if ( colours == ZM_COLOUR_GRAY8 ) { + } else if (colours == ZM_COLOUR_GRAY8) { subpixelorder = ZM_SUBPIX_ORDER_NONE; mImgPixFmt = AV_PIX_FMT_GRAY8; - mBpp = 1; } else { Panic("Unexpected colours: %d", colours); } - if ( capture ) - Initialise(); + if (capture) { + Debug(3, "Initializing Client"); + bind_libvnc_symbols(); + scale.init(); + } } - VncCamera::~VncCamera() { - if ( capture ) - Terminate(); -} - -void VncCamera::Initialise() { - Debug(2, "Initializing Client"); - bind_libvnc_symbols(); - mRfb = (*rfbGetClient_f)(8, 3, 4); - - (*rfbClientSetClientData_f)(mRfb, &TAG_0, &mVncData); - (*rfbClientSetClientData_f)(mRfb, &TAG_1, (void *)mPass.c_str()); - (*rfbClientSetClientData_f)(mRfb, &TAG_2, (void *)mUser.c_str()); - - mRfb->GotFrameBufferUpdate = GotFrameBufferUpdateCallback; - mRfb->GetPassword = GetPasswordCallback; - mRfb->GetCredential = GetCredentialsCallback; - - mRfb->programName = "Zoneminder VNC Monitor"; - mRfb->serverHost = strdup(mHost.c_str()); - mRfb->serverPort = atoi(mPort.c_str()); - scale.init(); -} - -void VncCamera::Terminate() { - if ( mRfb->frameBuffer ) - free(mRfb->frameBuffer); - (*rfbClientCleanup_f)(mRfb); - return; + if (capture and mRfb) { + if (mRfb->frameBuffer) + free(mRfb->frameBuffer); + (*rfbClientCleanup_f)(mRfb); + } + if (libvnc_lib) { + dlclose(libvnc_lib); + libvnc_lib = nullptr; + } } int VncCamera::PrimeCapture() { + if (libvnc_lib == nullptr) { + Error("No libvnc shared lib bound."); + return -1; + } Debug(1, "Priming capture from %s", mHost.c_str()); - if ( ! (*rfbInitClient_f)(mRfb, 0, nullptr) ) { + + if (!mRfb) { + mVncData.buffer = nullptr; + mVncData.width = 0; + mVncData.height = 0; + + // TODO, support 8bit or 24bit + mRfb = (*rfbGetClient_f)(8 /* bits per sample */, 3 /* samples per pixel */, 4 /* bytes Per Pixel */); + mRfb->MallocFrameBuffer = resize; + + (*rfbClientSetClientData_f)(mRfb, &TAG_0, &mVncData); + (*rfbClientSetClientData_f)(mRfb, &TAG_1, (void *)mPass.c_str()); + (*rfbClientSetClientData_f)(mRfb, &TAG_2, (void *)mUser.c_str()); + + mRfb->GotFrameBufferUpdate = GotFrameBufferUpdateCallback; + mRfb->GetPassword = GetPasswordCallback; + mRfb->GetCredential = GetCredentialsCallback; + + mRfb->programName = "Zoneminder VNC Monitor"; + if (mRfb->serverHost) free(mRfb->serverHost); + mRfb->serverHost = strdup(mHost.c_str()); + mRfb->serverPort = atoi(mPort.c_str()); + if (!mRfb->serverPort) { + Debug(1, "Defaulting to port 5900"); + mRfb->serverPort = 5900; + } + + } else { + Debug(1, "mRfb already exists?"); + } + if (!(*rfbInitClient_f)(mRfb, 0, nullptr)) { + /* IF rfbInitClient fails, it calls rdbClientCleanup which will free mRfb */ + mRfb = nullptr; return -1; } - return 0; + if (((unsigned int)mRfb->width != width) or ((unsigned int)mRfb->height != height)) { + Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)", + width, height, mRfb->width, mRfb->height); + } + getVideoStream(); + + return 1; } int VncCamera::PreCapture() { - Debug(2, "PreCapture"); int rc = (*WaitForMessage_f)(mRfb, 500); - if ( rc < 0 ) { + if (rc < 0) { return -1; - } else if ( !rc ) { + } else if (!rc) { return rc; } rfbBool res = (*HandleRFBServerMessage_f)(mRfb); + Debug(3, "PreCapture rc from HandleMessage %d", res == TRUE ? 1 : -1); return res == TRUE ? 1 : -1; } -int VncCamera::Capture(Image &image) { - Debug(2, "Capturing"); - uint8_t *directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - scale.Convert( - mVncData.buffer, - mRfb->si.framebufferHeight * mRfb->si.framebufferWidth * 4, +int VncCamera::Capture(ZMPacket &zm_packet) { + if (!mVncData.buffer) { + Debug(1, "No buffer"); + return 0; + } + if (!zm_packet.image) { + Debug(1, "Allocating image %dx%d %dcolours = %d", width, height, colours, colours*pixels); + zm_packet.image = new Image(width, height, colours, subpixelorder); + } + zm_packet.keyframe = 1; + zm_packet.codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet.packet.stream_index = mVideoStreamId; + zm_packet.stream = mVideoStream; + + uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); + Debug(1, "scale src %p, %d, dest %p %d %d %dx%d %dx%d", mVncData.buffer, + mRfb->si.framebufferWidth * mRfb->si.framebufferHeight * 4, directbuffer, - width * height * mBpp, + width * height * colours, + mImgPixFmt, + mRfb->si.framebufferWidth, + mRfb->si.framebufferHeight, + width, + height); + + int rc = scale.Convert( + mVncData.buffer, + mRfb->si.framebufferWidth * mRfb->si.framebufferHeight * 4, + //SWScale::GetBufferSize(AV_PIX_FMT_RGBA, mRfb->si.framebufferWidth, mRfb->si.framebufferHeight), + directbuffer, + width * height * colours, AV_PIX_FMT_RGBA, mImgPixFmt, mRfb->si.framebufferWidth, mRfb->si.framebufferHeight, width, height); - return 1; + return rc == 0 ? 1 : rc; } int VncCamera::PostCapture() { - return 0; -} - -int VncCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) { - return 0; + return 1; } int VncCamera::Close() { - return 0; + return 1; } #endif diff --git a/src/zm_libvnc_camera.h b/src/zm_libvnc_camera.h index 250cfaa2e..caa1bb1da 100644 --- a/src/zm_libvnc_camera.h +++ b/src/zm_libvnc_camera.h @@ -2,16 +2,21 @@ #ifndef ZN_LIBVNC_CAMERA_H #define ZN_LIBVNC_CAMERA_H -#include "zm_buffer.h" #include "zm_camera.h" -#include "zm_thread.h" #include "zm_swscale.h" #if HAVE_LIBVNC #include + +// Older versions of libvncserver defined a max macro in rfb/rfbproto.h +// Undef it here so it doesn't collide with std::max +// TODO: Remove this once xenial support is dropped +#ifdef max +#undef max +#endif + // Used by vnc callbacks -struct VncPrivateData -{ +struct VncPrivateData { uint8_t *buffer; uint8_t width; uint8_t height; @@ -21,7 +26,6 @@ class VncCamera : public Camera { protected: rfbClient *mRfb; VncPrivateData mVncData; - int mBpp; SWScale scale; AVPixelFormat mImgPixFmt; std::string mHost; @@ -30,7 +34,7 @@ protected: std::string mPass; public: VncCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &host, const std::string &port, const std::string &user, @@ -43,19 +47,15 @@ public: int p_hue, int p_colour, bool p_capture, - bool p_record_audio ); + bool p_record_audio); ~VncCamera(); - void Initialise(); - void Terminate(); - - int PreCapture(); - int PrimeCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); - int Close(); + int PreCapture() override; + int PrimeCapture() override; + int Capture(ZMPacket &packet) override; + int PostCapture() override; + int Close() override; }; #endif // HAVE_LIBVNC diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index 38c7dae0e..7e97ecd08 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -17,20 +17,15 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if ZM_HAS_V4L - #include "zm_local_camera.h" -#include -#include +#include "zm_packet.h" +#include "zm_utils.h" #include -#include #include -#include -#include -#include +#include + +#if ZM_HAS_V4L /* Workaround for GNU/kFreeBSD and FreeBSD */ #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) @@ -40,6 +35,7 @@ #endif static unsigned int BigEndian; +static bool primed; static int vidioctl(int fd, int request, void *arg) { int result = -1; @@ -148,6 +144,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { Fatal("Can't find swscale format for palette %d", palette); break; +#if 0 // These are all spare and may match some of the above pixFormat = AV_PIX_FMT_YUVJ420P; pixFormat = AV_PIX_FMT_YUVJ422P; @@ -173,6 +170,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) //pixFormat = AV_PIX_FMT_VDPAU_H264; //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; +#endif } } // end switch palette } // end if v4l2 @@ -215,6 +213,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { Fatal("Can't find swscale format for palette %d", palette); break; +#if 0 // These are all spare and may match some of the above pixFormat = AV_PIX_FMT_YUVJ420P; pixFormat = AV_PIX_FMT_YUVJ422P; @@ -241,6 +240,7 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) //pixFormat = AV_PIX_FMT_VDPAU_H264; //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; +#endif } } // end switch palette } // end if v4l1 @@ -285,7 +285,6 @@ static const uint32_t prefered_gray8_formats[] = { }; #endif - int LocalCamera::camera_count = 0; int LocalCamera::channel_count = 0; int LocalCamera::channels[VIDEO_MAX_FRAME]; @@ -308,7 +307,7 @@ AVFrame **LocalCamera::capturePictures = nullptr; LocalCamera *LocalCamera::last_camera = nullptr; LocalCamera::LocalCamera( - int p_id, + const Monitor *monitor, const std::string &p_device, int p_channel, int p_standard, @@ -324,15 +323,15 @@ LocalCamera::LocalCamera( int p_hue, int p_colour, bool p_capture, - bool p_record_audio, + bool p_record_audio, unsigned int p_extras) : - Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), - device( p_device ), - channel( p_channel ), - standard( p_standard ), - palette( p_palette ), - channel_index( 0 ), - extras ( p_extras ) + Camera(monitor, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio), + device(p_device), + channel(p_channel), + standard(p_standard), + palette(p_palette), + channel_index(0), + extras(p_extras) { // If we are the first, or only, input on this device then // do the initial opening etc @@ -343,10 +342,10 @@ LocalCamera::LocalCamera( if ( capture ) { if ( device_prime ) { - Debug( 2, "V4L support enabled, using V4L%d api", v4l_version ); + Debug(2, "V4L support enabled, using V4L%d api", v4l_version); } - if ( !last_camera || channel != last_camera->channel ) { + if ( (!last_camera) || (channel != last_camera->channel) ) { // We are the first, or only, input that uses this channel channel_prime = true; channel_index = channel_count++; @@ -362,10 +361,10 @@ LocalCamera::LocalCamera( uint32_t checkval = 0xAABBCCDD; if ( *(unsigned char*)&checkval == 0xDD ) { BigEndian = 0; - Debug(2,"little-endian processor detected"); + Debug(2, "little-endian processor detected"); } else if ( *(unsigned char*)&checkval == 0xAA ) { BigEndian = 1; - Debug(2,"Big-endian processor detected"); + Debug(2, "Big-endian processor detected"); } else { Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; @@ -388,19 +387,19 @@ LocalCamera::LocalCamera( } #endif - if ( capture ) { - if ( last_camera ) { - if ( (p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1) ) - Fatal( "Different Video For Linux version used for monitors sharing same device" ); + if (capture) { + if (last_camera) { + if ((p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1)) + Fatal("Different Video For Linux version used for monitors sharing same device"); - if ( standard != last_camera->standard ) - Warning( "Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong" ); + if (standard != last_camera->standard) + Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong"); - if ( palette != last_camera->palette ) - Warning( "Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); + if (palette != last_camera->palette) + Warning("Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong"); - if ( width != last_camera->width || height != last_camera->height ) - Warning( "Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); + if (width != last_camera->width or height != last_camera->height) + Warning("Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong"); } #if HAVE_LIBSWSCALE @@ -428,10 +427,6 @@ LocalCamera::LocalCamera( /* RGB24 palette and 24bit target colourspace */ } else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - - /* BGR24 palette and 24bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24 ) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; @@ -452,7 +447,7 @@ LocalCamera::LocalCamera( #if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ conversion_type = 1; - Debug(2,"Using swscale for image conversion"); + Debug(2, "Using swscale for image conversion"); if ( colours == ZM_COLOUR_RGB32 ) { subpixelorder = ZM_SUBPIX_ORDER_RGBA; imagePixFormat = AV_PIX_FMT_RGBA; @@ -595,15 +590,15 @@ LocalCamera::LocalCamera( conversion_type = 2; /* Try ZM format conversions */ } } -#else - /* Don't have swscale, see what we can do */ - conversion_type = 2; -#endif + /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ if ( colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) ) { conversion_type = 2; } - +#else + /* Don't have swscale, see what we can do */ + conversion_type = 2; +#endif if ( conversion_type == 2 ) { Debug(2,"Using ZM for image conversion"); if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8 ) { @@ -645,17 +640,17 @@ LocalCamera::LocalCamera( } else { Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); } - } + } // end if conversion_type == 2 } } #endif // ZM_HAS_V4L1 last_camera = this; - Debug(3,"Selected subpixelorder: %u",subpixelorder); + Debug(3, "Selected subpixelorder: %u", subpixelorder); #if HAVE_LIBSWSCALE /* Initialize swscale stuff */ - if ( capture && conversion_type == 1 ) { + if ( capture and (conversion_type == 1) ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) tmpPicture = av_frame_alloc(); #else @@ -670,10 +665,13 @@ LocalCamera::LocalCamera( unsigned int pSize = avpicture_get_size(imagePixFormat, width, height); #endif if ( pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %u", pSize, imagesize); + Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } - imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); + imgConversionContext = sws_getContext( + width, height, capturePixFormat, + width, height, imagePixFormat, SWS_BICUBIC, + nullptr, nullptr, nullptr); if ( !imgConversionContext ) { Fatal("Unable to initialise image scaling context"); @@ -681,8 +679,10 @@ LocalCamera::LocalCamera( } else { tmpPicture = nullptr; imgConversionContext = nullptr; - } + } // end if capture and conversion_tye == swscale #endif + if ( capture and device_prime ) + Initialise(); } // end LocalCamera::LocalCamera LocalCamera::~LocalCamera() { @@ -691,23 +691,17 @@ LocalCamera::~LocalCamera() { #if HAVE_LIBSWSCALE /* Clean up swscale stuff */ - if ( capture && conversion_type == 1 ) { + if ( capture && (conversion_type == 1) ) { sws_freeContext(imgConversionContext); imgConversionContext = nullptr; av_frame_free(&tmpPicture); } #endif -} + +} // end LocalCamera::~LocalCamera void LocalCamera::Initialise() { -#if HAVE_LIBSWSCALE - if ( logDebugging() ) - av_log_set_level(AV_LOG_DEBUG); - else - av_log_set_level(AV_LOG_QUIET); -#endif // HAVE_LIBSWSCALE - Debug(3, "Opening video device %s", device.c_str()); //if ( (vid_fd = open( device.c_str(), O_RDWR|O_NONBLOCK, 0 )) < 0 ) if ( (vid_fd = open(device.c_str(), O_RDWR, 0)) < 0 ) @@ -781,7 +775,7 @@ void LocalCamera::Initialise() { } } else { if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Fatal("Failed to set video format: %s", strerror(errno)); + Error("Failed to set video format: %s", strerror(errno)); } } @@ -807,6 +801,13 @@ void LocalCamera::Initialise() { , v4l2_data.fmt.fmt.pix.priv ); + if ( v4l2_data.fmt.fmt.pix.width != width ) { + Warning("Failed to set requested width"); + } + if ( v4l2_data.fmt.fmt.pix.height != height ) { + Warning("Failed to set requested height"); + } + /* Buggy driver paranoia. */ unsigned int min; min = v4l2_data.fmt.fmt.pix.width * 2; @@ -840,8 +841,8 @@ void LocalCamera::Initialise() { if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno)); } else { - Debug(4, "JPEG quality: %d",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + Debug(4, "JPEG quality: %d, markers: %#x", + jpeg_comp.quality, jpeg_comp.jpeg_markers); } } } @@ -924,7 +925,8 @@ void LocalCamera::Initialise() { #else avpicture_fill( (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, + (uint8_t*)v4l2_data.buffers[i].start, + capturePixFormat, v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height ); @@ -949,12 +951,12 @@ void LocalCamera::Initialise() { } if ( (input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN) ) { - Fatal("Device does not support video standard %d", standard); + Error("Device does not support video standard %d", standard); } stdId = standard; - if ( (input.std != V4L2_STD_UNKNOWN) && (vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0) ) { - Fatal("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); + if ((vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0)) { + Error("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); } Contrast(contrast); @@ -1172,11 +1174,12 @@ void LocalCamera::Terminate() { Error("Failed to munmap buffers: %s", strerror(errno)); delete[] v4l1_data.buffers; - } + } // end if using v4l1 #endif // ZM_HAS_V4L1 close(vid_fd); -} // end Terminate + primed = false; +} // end LocalCamera::Terminate uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Automatic format selection */ @@ -1268,13 +1271,16 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { #endif /* ZM_HAS_V4L2 */ return selected_palette; -} - +} //uint32_t LocalCamera::AutoSelectFormat(int p_colours) #define capString(test,prefix,yesString,noString,capability) \ (test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n") -bool LocalCamera::GetCurrentSettings(const char *device, char *output, int version, bool verbose) { +bool LocalCamera::GetCurrentSettings( + const char *device, + char *output, + int version, + bool verbose) { output[0] = 0; char *output_ptr = output; @@ -1412,20 +1418,20 @@ bool LocalCamera::GetCurrentSettings(const char *device, char *output, int versi if ( verbose ) output_ptr += sprintf( output_ptr, - " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + " %s (0x%02x%02x%02x%02x)\n", format.description, - (format.pixelformat>>24)&0xff, - (format.pixelformat>>16)&0xff, - (format.pixelformat>>8)&0xff, - format.pixelformat&0xff); + (format.pixelformat >> 24) & 0xff, + (format.pixelformat >> 16) & 0xff, + (format.pixelformat >> 8) & 0xff, + format.pixelformat & 0xff); else output_ptr += sprintf( output_ptr, - "0x%02hhx%02hhx%02hhx%02hhx/", - (format.pixelformat>>24)&0xff, - (format.pixelformat>>16)&0xff, - (format.pixelformat>>8)&0xff, - (format.pixelformat)&0xff); + "0x%02x%02x%02x%02x/", + (format.pixelformat >> 24) & 0xff, + (format.pixelformat >> 16) & 0xff, + (format.pixelformat >> 8) & 0xff, + format.pixelformat & 0xff); } while ( formatIndex++ >= 0 ); if ( !verbose ) @@ -1766,7 +1772,7 @@ bool LocalCamera::GetCurrentSettings(const char *device, char *output, int versi return true; } -int LocalCamera::Brightness( int p_brightness ) { +int LocalCamera::Brightness(int p_brightness) { #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1820,7 +1826,7 @@ int LocalCamera::Brightness( int p_brightness ) { return -1; } -int LocalCamera::Hue( int p_hue ) { +int LocalCamera::Hue(int p_hue) { #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { struct v4l2_control vid_control; @@ -1830,9 +1836,9 @@ int LocalCamera::Hue( int p_hue ) { if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { if ( errno != EINVAL ) - Error("Unable to query hue: %s", strerror(errno)) + Error("Unable to query hue: %s", strerror(errno)); else - Warning("Hue control is not supported") + Warning("Hue control is not supported"); } else if ( p_hue >= 0 ) { vid_control.value = p_hue; @@ -1973,23 +1979,30 @@ int LocalCamera::Contrast( int p_contrast ) { } int LocalCamera::PrimeCapture() { - Initialise(); + getVideoStream(); + if ( !device_prime ) + return 1; Debug(2, "Priming capture"); #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { - Debug(3, "Queueing buffers"); + Debug(3, "Queueing (%d) buffers", v4l2_data.reqbufs.count); for ( unsigned int frame = 0; frame < v4l2_data.reqbufs.count; frame++ ) { struct v4l2_buffer vid_buf; memset(&vid_buf, 0, sizeof(vid_buf)); + if ( v4l2_data.fmt.type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) { + Warning("Unknown type: (%d)", v4l2_data.fmt.type); + } vid_buf.type = v4l2_data.fmt.type; vid_buf.memory = v4l2_data.reqbufs.memory; vid_buf.index = frame; - if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) - Fatal("Failed to queue buffer %d: %s", frame, strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) { + Error("Failed to queue buffer %d: %s", frame, strerror(errno)); + return 0; + } } v4l2_data.bufptr = nullptr; @@ -1997,9 +2010,11 @@ int LocalCamera::PrimeCapture() { //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //enum v4l2_buf_type type = v4l2_data.fmt.type; enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; - if ( vidioctl(vid_fd, VIDIOC_STREAMON, &type) < 0 ) - Fatal("Failed to start capture stream: %s", strerror(errno)); - } + if (vidioctl(vid_fd, VIDIOC_STREAMON, &type) < 0) { + Error("Failed to start capture stream: %s", strerror(errno)); + return -1; + } + } // end if v4l_version == 2 #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { @@ -2013,16 +2028,16 @@ int LocalCamera::PrimeCapture() { } #endif // ZM_HAS_V4L1 - return 0; + return 1; } // end LocalCamera::PrimeCapture int LocalCamera::PreCapture() { //Debug(5, "Pre-capturing"); - return 0; + return 1; } -int LocalCamera::Capture(Image &image) { - Debug(3, "Capturing"); +int LocalCamera::Capture(ZMPacket &zm_packet) { + // We assume that the avpacket is allocated, and just needs to be filled static uint8_t* buffer = nullptr; int buffer_bytesused = 0; int capture_frame = -1; @@ -2044,7 +2059,6 @@ int LocalCamera::Capture(Image &image) { memset(&vid_buf, 0, sizeof(vid_buf)); vid_buf.type = v4l2_data.fmt.type; - //vid_buf.memory = V4L2_MEMORY_MMAP; vid_buf.memory = v4l2_data.reqbufs.memory; Debug(3, "Capturing %d frames", captures_per_frame); @@ -2057,6 +2071,7 @@ int LocalCamera::Capture(Image &image) { } return -1; } + Debug(5, "Captured a frame"); v4l2_data.bufptr = &vid_buf; capture_frame = v4l2_data.bufptr->index; @@ -2076,11 +2091,17 @@ int LocalCamera::Capture(Image &image) { buffer_bytesused = v4l2_data.bufptr->bytesused; bytes += buffer_bytesused; - if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", - v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); + if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) > (width * height) ) { + Fatal("Captured image dimensions larger than image buffer: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); + } else if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { + Error("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d", + v4l2_data.fmt.fmt.pix.width, v4l2_data.fmt.fmt.pix.height, width, height); } } // end if v4l2 +#if ZM_HAS_V4L1 + else +#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { @@ -2108,64 +2129,6 @@ int LocalCamera::Capture(Image &image) { buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; } #endif // ZM_HAS_V4L1 - } /* prime capture */ - - if ( conversion_type != 0 ) { - - Debug(3, "Performing format conversion"); - - /* Request a writeable buffer of the target image */ - uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == nullptr ) { - Error("Failed requesting writeable buffer for the captured image."); - return -1; - } -#if HAVE_LIBSWSCALE - if ( conversion_type == 1 ) { - - Debug(9, "Calling sws_scale to perform the conversion"); - /* Use swscale to convert the image directly into the shared memory */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(tmpPicture->data, - tmpPicture->linesize, directbuffer, - imagePixFormat, width, height, 1); -#else - avpicture_fill( (AVPicture *)tmpPicture, directbuffer, - imagePixFormat, width, height ); -#endif - sws_scale( imgConversionContext, - capturePictures[capture_frame]->data, - capturePictures[capture_frame]->linesize, - 0, - height, - tmpPicture->data, - tmpPicture->linesize ); - } -#endif - if ( conversion_type == 2 ) { - Debug(9, "Calling the conversion function"); - /* Call the image conversion function and convert directly into the shared memory */ - (*conversion_fptr)(buffer, directbuffer, pixels); - } else if ( conversion_type == 3 ) { - Debug(9, "Decoding the JPEG image"); - /* JPEG decoding */ - image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); - } - - } else { - Debug(3, "No format conversion performed. Assigning the image"); - - /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ - image.Assign( width, height, colours, subpixelorder, buffer, imagesize); - } - - return 1; -} // end int LocalCamera::Capture() - -int LocalCamera::PostCapture() { - Debug(4, "Post-capturing"); - // Requeue the buffer unless we need to switch or are a duplicate camera on a channel - if ( channel_count > 1 || channel_prime ) { #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { if ( channel_count > 1 ) { @@ -2177,9 +2140,8 @@ int LocalCamera::PostCapture() { } v4l2_std_id stdId = standards[next_channel]; - if ( vidioctl( vid_fd, VIDIOC_S_STD, &stdId ) < 0 ) { + if ( vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0 ) { Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); - return -1; } } if ( v4l2_data.bufptr ) { @@ -2189,9 +2151,12 @@ int LocalCamera::PostCapture() { return -1; } } else { - Error("Unable to requeue buffer due to not v4l2_data") + Error("Unable to requeue buffer due to not v4l2_data"); } } +#if ZM_HAS_V4L1 + else +#endif // ZM_HAS_V4L1 #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { @@ -2223,8 +2188,72 @@ int LocalCamera::PostCapture() { v4l1_data.active_frame = (v4l1_data.active_frame+1)%v4l1_data.frames.frames; } #endif // ZM_HAS_V4L1 - } - return 0; -} + } /* prime capture */ + + if (!zm_packet.image) { + Debug(4, "Allocating image"); + zm_packet.image = new Image(width, height, colours, subpixelorder); + } + + if ( conversion_type != 0 ) { + Debug(3, "Performing format conversion %d", conversion_type); + + /* Request a writeable buffer of the target image */ + uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); + if ( directbuffer == nullptr ) { + Error("Failed requesting writeable buffer for the captured image."); + return -1; + } +#if HAVE_LIBSWSCALE + if ( conversion_type == 1 ) { + Debug(9, "Calling sws_scale to perform the conversion"); + /* Use swscale to convert the image directly into the shared memory */ +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(tmpPicture->data, + tmpPicture->linesize, directbuffer, + imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)tmpPicture, directbuffer, + imagePixFormat, width, height ); +#endif + sws_scale( + imgConversionContext, + capturePictures[capture_frame]->data, + capturePictures[capture_frame]->linesize, + 0, + height, + tmpPicture->data, + tmpPicture->linesize + ); + } else +#endif + if ( conversion_type == 2 ) { + Debug(9, "Calling the conversion function"); + /* Call the image conversion function and convert directly into the shared memory */ + (*conversion_fptr)(buffer, directbuffer, pixels); + } else if ( conversion_type == 3 ) { + // Need to store the jpeg data too + Debug(9, "Decoding the JPEG image"); + /* JPEG decoding */ + zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); + } + + } else { + Debug(3, "No format conversion performed. Assigning the image"); + + /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ + zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + } // end if doing conversion or not + + zm_packet.packet.stream_index = mVideoStreamId; + zm_packet.stream = mVideoStream; + zm_packet.codec_type = AVMEDIA_TYPE_VIDEO; + zm_packet.keyframe = 1; + return 1; +} // end int LocalCamera::Capture() + +int LocalCamera::PostCapture() { + return 1; +} #endif // ZM_HAS_V4L diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index d06631c23..6d3ff2672 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -20,10 +20,7 @@ #ifndef ZM_LOCAL_CAMERA_H #define ZM_LOCAL_CAMERA_H -#include "zm.h" #include "zm_camera.h" -#include "zm_image.h" -#include "zm_packetqueue.h" #if ZM_HAS_V4L @@ -42,25 +39,20 @@ #define VIDEO_MAX_FRAME 32 #endif -#include "zm_ffmpeg.h" - // // Class representing 'local' cameras, i.e. those which are // directly connect to the host machine and which are accessed // via a video interface. // -class LocalCamera : public Camera -{ +class LocalCamera : public Camera { protected: #if ZM_HAS_V4L2 - struct V4L2MappedBuffer - { + struct V4L2MappedBuffer { void *start; size_t length; }; - struct V4L2Data - { + struct V4L2Data { v4l2_cropcap cropcap; v4l2_crop crop; v4l2_format fmt; @@ -71,8 +63,7 @@ protected: #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - struct V4L1Data - { + struct V4L1Data { int active_frame; video_mbuf frames; video_mmap *buffers; @@ -123,7 +114,7 @@ protected: public: LocalCamera( - int p_id, + const Monitor *monitor, const std::string &device, int p_channel, int p_format, @@ -146,26 +137,25 @@ public: void Initialise(); void Terminate(); - const std::string &Device() const { return( device ); } + const std::string &Device() const { return device; } - int Channel() const { return( channel ); } - int Standard() const { return( standard ); } - int Palette() const { return( palette ); } - int Extras() const { return( extras ); } + int Channel() const { return channel; } + int Standard() const { return standard; } + int Palette() const { return palette; } + int Extras() const { return extras; } - int Brightness( int p_brightness=-1 ); - int Hue( int p_hue=-1 ); - int Colour( int p_colour=-1 ); - int Contrast( int p_contrast=-1 ); + int Brightness( int p_brightness=-1 ) override; + int Hue( int p_hue=-1 ) override; + int Colour( int p_colour=-1 ) override; + int Contrast( int p_contrast=-1 ) override; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; + int Close() override { return 0; }; - static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); + static bool GetCurrentSettings(const char *device, char *output, int version, bool verbose); }; #endif // ZM_HAS_V4L diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 29361b5a9..dd03ec9f2 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -19,23 +19,21 @@ #include "zm_logger.h" -#include "zm_config.h" -#include "zm_utils.h" #include "zm_db.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "zm_utils.h" #include +#include +#include +#include + #ifdef __FreeBSD__ #include #endif +#include +#include +#include +#include + bool Logger::smInitialised = false; Logger *Logger::smInstance = nullptr; @@ -43,18 +41,6 @@ Logger *Logger::smInstance = nullptr; Logger::StringMap Logger::smCodes; Logger::IntMap Logger::smSyslogPriorities; -#if 0 -static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) { - tp1->tv_sec -= tp2->tv_sec; - if ( tp1->tv_usec <= tp2->tv_usec ) { - tp1->tv_sec--; - tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); - } else { - tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; - } -} -#endif - void Logger::usrHandler(int sig) { Logger *logger = fetch(); if ( sig == SIGUSR1 ) @@ -73,16 +59,15 @@ Logger::Logger() : mEffectiveLevel(NOLOG), mDbConnected(false), mLogPath(staticConfig.PATH_LOGS.c_str()), - //mLogFile( mLogPath+"/"+mId+".log" ), + // mLogFile( mLogPath+"/"+mId+".log" ), mLogFileFP(nullptr), mHasTerminal(false), mFlush(false) { - - if ( smInstance ) { + if (smInstance) { Panic("Attempt to create second instance of Logger class"); } - if ( !smInitialised ) { + if (!smInitialised) { smCodes[INFO] = "INF"; smCodes[WARNING] = "WAR"; smCodes[ERROR] = "ERR"; @@ -97,16 +82,16 @@ Logger::Logger() : smSyslogPriorities[PANIC] = LOG_ERR; char code[4] = ""; - for ( int i = DEBUG1; i <= DEBUG9; i++ ) { + for (int i = DEBUG1; i <= DEBUG9; i++) { snprintf(code, sizeof(code), "DB%d", i); smCodes[i] = code; smSyslogPriorities[i] = LOG_DEBUG; } smInitialised = true; - } + } // end if ! smInitialised - if ( fileno(stderr) && isatty(fileno(stderr)) ) { + if (fileno(stderr) && isatty(fileno(stderr))) { mHasTerminal = true; mTerminalLevel = WARNING; } @@ -117,14 +102,6 @@ Logger::~Logger() { smCodes.clear(); smSyslogPriorities.clear(); smInitialised = false; -#if 0 - for ( StringMap::iterator itr = smCodes.begin(); itr != smCodes.end(); itr ++ ) { - smCodes.erase( itr ); - } - for ( IntMap::iterator itr = smSyslogPriorities.begin(); itr != smSyslogPriorities.end(); itr ++ ) { - smSyslogPriorities.erase(itr); - } -#endif } void Logger::initialise(const std::string &id, const Options &options) { @@ -149,24 +126,24 @@ void Logger::initialise(const std::string &id, const Options &options) { Level tempLevel = INFO; Level tempTerminalLevel = mTerminalLevel; - Level tempDatabaseLevel = mDatabaseLevel; - Level tempFileLevel = mFileLevel; - Level tempSyslogLevel = mSyslogLevel; if ( options.mTerminalLevel != NOOPT ) tempTerminalLevel = options.mTerminalLevel; - // DEBUG1 == 1. So >= DEBUG1, we set to DEBUG9?! Why? + // DEBUG1 == 1. So >= DEBUG1, we set to DEBUG9?! Why? icon: because log_level_database only goes up to debug. + Level tempDatabaseLevel; if ( options.mDatabaseLevel != NOOPT ) tempDatabaseLevel = options.mDatabaseLevel; else tempDatabaseLevel = config.log_level_database >= DEBUG1 ? DEBUG9 : config.log_level_database; + Level tempFileLevel; if ( options.mFileLevel != NOOPT ) tempFileLevel = options.mFileLevel; else tempFileLevel = config.log_level_file >= DEBUG1 ? DEBUG9 : config.log_level_file; + Level tempSyslogLevel; if ( options.mSyslogLevel != NOOPT ) tempSyslogLevel = options.mSyslogLevel; else @@ -189,7 +166,7 @@ void Logger::initialise(const std::string &id, const Options &options) { tempSyslogLevel = atoi(envPtr); if ( config.log_debug ) { - StringVector targets = split(config.log_debug_target, "|"); + StringVector targets = Split(config.log_debug_target, "|"); for ( unsigned int i = 0; i < targets.size(); i++ ) { const std::string &target = targets[i]; if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "" ) { @@ -201,7 +178,7 @@ void Logger::initialise(const std::string &id, const Options &options) { } } } - } // end foreach target + } // end foreach target } else { // if we don't have debug turned on, then the max effective log level is INFO if ( tempSyslogLevel > INFO ) tempSyslogLevel = INFO; @@ -209,7 +186,7 @@ void Logger::initialise(const std::string &id, const Options &options) { if ( tempTerminalLevel > INFO ) tempTerminalLevel = INFO; if ( tempDatabaseLevel > INFO ) tempDatabaseLevel = INFO; if ( tempLevel > INFO ) tempLevel = INFO; - } // end if config.log_debug + } // end if config.log_debug logFile(tempLogFile); @@ -283,9 +260,7 @@ std::string Logger::strEnv(const std::string &name, const std::string &defaultVa } char *Logger::getTargettedEnv(const std::string &name) { - std::string envName; - - envName = name+"_"+mId; + std::string envName = name+"_"+mId; char *envPtr = getenv(envName.c_str()); if ( !envPtr && mId != mIdRoot ) { envName = name+"_"+mIdRoot; @@ -322,9 +297,7 @@ const std::string &Logger::id(const std::string &id) { Logger::Level Logger::level(Logger::Level level) { if ( level > NOOPT ) { - level = limit(level); - if ( mLevel != level ) - mLevel = level; + mLevel = limit(level); mEffectiveLevel = NOLOG; if ( mTerminalLevel > mEffectiveLevel ) @@ -349,35 +322,33 @@ Logger::Level Logger::terminalLevel(Logger::Level terminalLevel) { if ( terminalLevel > NOOPT ) { if ( !mHasTerminal ) terminalLevel = NOLOG; - terminalLevel = limit(terminalLevel); - if ( mTerminalLevel != terminalLevel ) - mTerminalLevel = terminalLevel; + mTerminalLevel = limit(terminalLevel); } return mTerminalLevel; } Logger::Level Logger::databaseLevel(Logger::Level databaseLevel) { - if ( databaseLevel > NOOPT ) { + if (databaseLevel > NOOPT) { databaseLevel = limit(databaseLevel); - if ( mDatabaseLevel != databaseLevel ) { - if ( (databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG) ) { // <= NOLOG would be NOOPT - if ( !zmDbConnect() ) { + if (mDatabaseLevel != databaseLevel) { + if ((databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG)) { // <= NOLOG would be NOOPT + if (!zmDbConnected) { databaseLevel = NOLOG; } - } // end if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) + } mDatabaseLevel = databaseLevel; - } // end if ( mDatabaseLevel != databaseLevel ) - } // end if ( databaseLevel > NOOPT ) + } + } return mDatabaseLevel; } Logger::Level Logger::fileLevel(Logger::Level fileLevel) { - if ( fileLevel > NOOPT ) { + if (fileLevel > NOOPT) { fileLevel = limit(fileLevel); // Always close, because we may have changed file names - if ( mFileLevel > NOLOG ) - closeFile(); + if (mFileLevel > NOLOG) + closeFile(); mFileLevel = fileLevel; // Don't try to open it here because it will create the log file even if we never write to it. } @@ -385,13 +356,13 @@ Logger::Level Logger::fileLevel(Logger::Level fileLevel) { } Logger::Level Logger::syslogLevel(Logger::Level syslogLevel) { - if ( syslogLevel > NOOPT ) { + if (syslogLevel > NOOPT) { syslogLevel = limit(syslogLevel); - if ( mSyslogLevel != syslogLevel ) { - if ( mSyslogLevel > NOLOG ) + if (mSyslogLevel != syslogLevel) { + if (mSyslogLevel > NOLOG) closeSyslog(); mSyslogLevel = syslogLevel; - if ( mSyslogLevel > NOLOG ) + if (mSyslogLevel > NOLOG) openSyslog(); } } @@ -401,31 +372,31 @@ Logger::Level Logger::syslogLevel(Logger::Level syslogLevel) { void Logger::logFile(const std::string &logFile) { bool addLogPid = false; std::string tempLogFile = logFile; - if ( tempLogFile[tempLogFile.length()-1] == '+' ) { + if (tempLogFile[tempLogFile.length()-1] == '+') { tempLogFile.resize(tempLogFile.length()-1); addLogPid = true; } - if ( addLogPid ) + if (addLogPid) mLogFile = stringtf("%s.%05d", tempLogFile.c_str(), getpid()); else mLogFile = tempLogFile; } void Logger::openFile() { - if ( mLogFile.size() ) { - if ( (mLogFileFP = fopen(mLogFile.c_str(), "a")) == nullptr ) { - mFileLevel = NOLOG; - Error("fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno)); - } + if (mLogFile.size()) { + if ( (mLogFileFP = fopen(mLogFile.c_str(), "a")) == nullptr ) { + mFileLevel = NOLOG; + Error("fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno)); + } } else { puts("Called Logger::openFile() without a filename"); } } void Logger::closeFile() { - if ( mLogFileFP ) { + if (mLogFileFP) { fflush(mLogFileFP); - if ( fclose(mLogFileFP) < 0 ) { + if (fclose(mLogFileFP) < 0) { mLogFileFP = nullptr; Error("fclose(), error = %s", strerror(errno)); } @@ -434,7 +405,6 @@ void Logger::closeFile() { } void Logger::closeDatabase() { - } void Logger::openSyslog() { @@ -445,25 +415,22 @@ void Logger::closeSyslog() { (void) closelog(); } -void Logger::logPrint(bool hex, const char * const filepath, const int line, const int level, const char *fstring, ...) { - - if ( level > mEffectiveLevel ) { - return; - } +void Logger::logPrint(bool hex, const char *filepath, int line, int level, const char *fstring, ...) { + if (level > mEffectiveLevel) return; + if (level < PANIC || level > DEBUG9) + Panic("Invalid logger level %d", level); log_mutex.lock(); + // Can we save some cycles by having these as members and not allocate them on the fly? I think so. char timeString[64]; - char logString[8192]; + char logString[4096]; // SQL TEXT can hold 64k so we could go up to 32k here but why? va_list argPtr; struct timeval timeVal; - char *filecopy = strdup(filepath); - const char * const file = basename(filecopy); + const char *base = strrchr(filepath, '/'); + const char *file = base ? base+1 : filepath; const char *classString = smCodes[level].c_str(); - if ( level < PANIC || level > DEBUG9 ) - Panic("Invalid logger level %d", level); - gettimeofday(&timeVal, nullptr); #if 0 @@ -476,7 +443,8 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con } else { #endif char *timePtr = timeString; - timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec)); + tm now_tm = {}; + timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime_r(&timeVal.tv_sec, &now_tm)); snprintf(timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec); #if 0 } @@ -492,12 +460,12 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con #else #ifdef HAVE_SYSCALL #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id + if ((syscall(SYS_thr_self, &tid)) < 0) // Thread/Process id # else // SOLARIS doesn't have SYS_gettid; don't assume #ifdef SYS_gettid - if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id + if ((tid = syscall(SYS_gettid)) < 0) // Thread/Process id #endif // SYS_gettid #endif #endif // HAVE_SYSCALL @@ -529,80 +497,70 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con } va_end(argPtr); char *syslogEnd = logPtr; + + if ( static_cast(logPtr - logString) >= sizeof(logString) ) { + // vsnprintf won't exceed the the buffer, but it might hit the end. + logPtr = logString + sizeof(logString)-3; + } strncpy(logPtr, "]\n", sizeof(logString)-(logPtr-logString)); - if ( level <= mTerminalLevel ) { + if (level <= mTerminalLevel) { puts(logString); fflush(stdout); } - if ( level <= mFileLevel ) { - if ( !mLogFileFP ) { - log_mutex.unlock(); + + if (level <= mFileLevel) { + if (!mLogFileFP) { + // FIXME unlocking here is a problem. Another thread could sneak in. + // We are using a recursive mutex so unlocking shouldn't be neccessary + //log_mutex.unlock(); + // We do this here so that we only create the file if we ever write to it. openFile(); - log_mutex.lock(); + //log_mutex.lock(); } - if ( mLogFileFP ) { + if (mLogFileFP) { fputs(logString, mLogFileFP); - if ( mFlush ) - fflush(mLogFileFP); - } else { - puts("Logging to file, but failed to open it\n"); + if (mFlush) fflush(mLogFileFP); + } else if (mTerminalLevel != NOLOG) { + puts("Logging to file but failed to open it\n"); } -#if 0 - } else { - printf("Not writing to log file because level %d %s <= mFileLevel %d %s\nstring: %s\n", - level, smCodes[level].c_str(), mFileLevel, smCodes[mFileLevel].c_str(), logString); -#endif - } - *syslogEnd = '\0'; - if ( level <= mDatabaseLevel ) { - char sql[ZM_SQL_MED_BUFSIZ]; - char escapedString[(strlen(syslogStart)*2)+1]; + } // end if level <= mFileLevel - if ( !db_mutex.trylock() ) { - mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart)); + if (level <= mDatabaseLevel) { + if (zmDbConnected) { + int syslogSize = syslogEnd-syslogStart; + char escapedString[(syslogSize*2)+1]; + mysql_real_escape_string(&dbconn, escapedString, syslogStart, syslogSize); - snprintf(sql, sizeof(sql), + std::string sql_string = stringtf( "INSERT INTO `Logs` " "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" - " VALUES " - "( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", - timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line - ); - if ( mysql_query(&dbconn, sql) ) { - Level tempDatabaseLevel = mDatabaseLevel; - databaseLevel(NOLOG); - Error("Can't insert log entry: sql(%s) error(%s)", sql, mysql_error(&dbconn)); - databaseLevel(tempDatabaseLevel); - } - db_mutex.unlock(); + " VALUES " + "( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", + timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line + ); + dbQueue.push(std::move(sql_string)); } else { - Level tempDatabaseLevel = mDatabaseLevel; - databaseLevel(NOLOG); - Error("Can't insert log entry: sql(%s) error(db is locked)", logString); - databaseLevel(tempDatabaseLevel); + puts("Db is closed"); } - } - if ( level <= mSyslogLevel ) { - int priority = smSyslogPriorities[level]; - //priority |= LOG_DAEMON; - syslog(priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart); + } // end if level <= mDatabaseLevel + + if (level <= mSyslogLevel) { + *syslogEnd = '\0'; + syslog(smSyslogPriorities[level], "%s [%s] [%s]", classString, mId.c_str(), syslogStart); } - free(filecopy); - if ( level <= FATAL ) { - log_mutex.unlock(); + log_mutex.unlock(); + if (level <= FATAL) { logTerm(); zmDbClose(); - if ( level <= PANIC ) - abort(); + if (level <= PANIC) abort(); exit(-1); } - log_mutex.unlock(); } // end logPrint void logInit(const char *name, const Logger::Options &options) { - if ( Logger::smInstance ) { + if (Logger::smInstance) { delete Logger::smInstance; Logger::smInstance = nullptr; } @@ -612,7 +570,7 @@ void logInit(const char *name, const Logger::Options &options) { } void logTerm() { - if ( Logger::smInstance ) { + if (Logger::smInstance) { delete Logger::smInstance; Logger::smInstance = nullptr; } diff --git a/src/zm_logger.h b/src/zm_logger.h index 82144a1c9..d5a01be39 100644 --- a/src/zm_logger.h +++ b/src/zm_logger.h @@ -20,17 +20,16 @@ #ifndef ZM_LOGGER_H #define ZM_LOGGER_H +#include "zm_db.h" #include "zm_config.h" -#include -#include -#include +#include "zm_define.h" #include +#include +#include + #ifdef HAVE_SYS_SYSCALL_H #include #endif // HAVE_SYS_SYSCALL_H -#include - -#include "zm_thread.h" class Logger { public: @@ -90,7 +89,7 @@ private: static bool smInitialised; static Logger *smInstance; - RecursiveMutex log_mutex; + std::recursive_mutex log_mutex; static StringMap smCodes; static IntMap smSyslogPriorities; @@ -121,7 +120,7 @@ private: Logger(); ~Logger(); - int limit(int level) { + int limit(const int level) const { if ( level > DEBUG9 ) return DEBUG9; if ( level < NOLOG ) @@ -154,18 +153,12 @@ public: void terminate(); const std::string &id(const std::string &id); - const std::string &id() const { - return mId; - } + const std::string &id() const { return mId; } - Level level() const { - return mLevel; - } + Level level() const { return mLevel; } Level level(Level=NOOPT); - bool debugOn() { - return mEffectiveLevel >= DEBUG1; - } + bool debugOn() const { return mEffectiveLevel >= DEBUG1; } Level terminalLevel(Level=NOOPT); Level databaseLevel(Level=NOOPT); @@ -180,8 +173,13 @@ private: void closeSyslog(); void closeDatabase(); -public: - void logPrint(bool hex, const char * const filepath, const int line, const int level, const char *fstring, ...); + public: + void logPrint(bool hex, + const char *filepath, + int line, + int level, + const char *fstring, + ...) __attribute__((format(printf, 6, 7))); }; void logInit(const char *name, const Logger::Options &options=Logger::Options()); @@ -196,15 +194,19 @@ inline Logger::Level logDebugging() { return Logger::fetch()->debugOn(); } -#define logPrintf(logLevel,params...) {\ - if ( logLevel <= Logger::fetch()->level() )\ - Logger::fetch()->logPrint( false, __FILE__, __LINE__, logLevel, ##params );\ - } +#define logPrintf(logLevel, params...) \ + do { \ + if (logLevel <= Logger::fetch()->level()) { \ + Logger::fetch()->logPrint(false, __FILE__, __LINE__, logLevel, ##params); \ + } \ + } while (0) -#define logHexdump(logLevel,data,len) {\ - if ( logLevel <= Logger::fetch()->level() )\ - Logger::fetch()->logPrint( true, __FILE__, __LINE__, logLevel, "%p (%d)", data, len );\ - } +#define logHexdump(logLevel, data, len) \ + do { \ + if (logLevel <= Logger::fetch()->level()) { \ + Logger::fetch()->logPrint(true, __FILE__, __LINE__, logLevel, "%p (%d)", data, len); \ + } \ + } while (0) /* Debug compiled out */ #ifndef DBG_OFF diff --git a/src/zm_mem_utils.h b/src/zm_mem_utils.h index 6ef1bdac5..72fb2b9aa 100644 --- a/src/zm_mem_utils.h +++ b/src/zm_mem_utils.h @@ -20,13 +20,12 @@ #ifndef ZM_MEM_UTILS_H #define ZM_MEM_UTILS_H -#include -#include "zm.h" +#include inline void* zm_mallocaligned(unsigned int reqalignment, size_t reqsize) { uint8_t* retptr; #if HAVE_POSIX_MEMALIGN - if ( posix_memalign((void**)&retptr,reqalignment,reqsize) != 0 ) + if ( posix_memalign((void**)&retptr, reqalignment, reqsize) != 0 ) return nullptr; return retptr; @@ -39,7 +38,7 @@ inline void* zm_mallocaligned(unsigned int reqalignment, size_t reqsize) { alloc = retptr + sizeof(void*); - if(((long)alloc % reqalignment) != 0) + if ( ((long)alloc % reqalignment) != 0 ) alloc = alloc + (reqalignment - ((long)alloc % reqalignment)); /* Store a pointer before to the start of the block, just before returned aligned memory */ diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index de245f854..9c58a8ffb 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -17,44 +17,48 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" #include "zm_monitor.h" -#include "zm_video.h" + +#include "zm_group.h" #include "zm_eventstream.h" -#if ZM_HAS_V4L -#include "zm_local_camera.h" -#endif // ZM_HAS_V4L +#include "zm_fifo.h" +#include "zm_file_camera.h" #include "zm_remote_camera.h" #include "zm_remote_camera_http.h" #include "zm_remote_camera_nvsocket.h" +#include "zm_signal.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +#if ZM_HAS_V4L +#include "zm_local_camera.h" +#endif // ZM_HAS_V4L + #if HAVE_LIBAVFORMAT #include "zm_remote_camera_rtsp.h" #endif // HAVE_LIBAVFORMAT -#include "zm_file_camera.h" + #if HAVE_LIBAVFORMAT #include "zm_ffmpeg_camera.h" #endif // HAVE_LIBAVFORMAT -#include "zm_fifo.h" + #if HAVE_LIBVLC #include "zm_libvlc_camera.h" #endif // HAVE_LIBVLC + #if HAVE_LIBCURL #include "zm_curl_camera.h" #endif // HAVE_LIBCURL + #if HAVE_LIBVNC #include "zm_libvnc_camera.h" #endif // HAVE_LIBVNC +#include +#include +#include + #if ZM_MEM_MAPPED #include #include @@ -71,19 +75,21 @@ // This is the official SQL (and ordering of the fields) to load a Monitor. // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = -"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, " -"`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," +"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, " +"`LinkedMonitors`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings -"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " +"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " "`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " "`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " -//" OutputCodec, Encoder, OutputContainer, " +"`OutputCodec`, `Encoder`, `OutputContainer`, " "`RecordAudio`, " "`Brightness`, `Contrast`, `Hue`, `Colour`, " "`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`," -"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " +"`ImageBufferCount`, `MaxImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " -"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`"; +"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," +"`RTSPServer`, `RTSPStreamName`," +"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-2 FROM `Monitors`"; std::string CameraType_Strings[] = { "Local", @@ -91,20 +97,22 @@ std::string CameraType_Strings[] = { "File", "Ffmpeg", "LibVLC", + "NVSOCKET", "CURL", "VNC", }; +std::string State_Strings[] = { + "IDLE", + "PREALARM", + "ALARM", + "ALERT", + "TAPE" +}; -std::vector split(const std::string &s, char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(trimSpaces(item)); - } - return elems; -} +std::string TriggerState_Strings[] = { + "Cancel", "On", "Off" +}; Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : id(p_id), @@ -116,14 +124,14 @@ Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : #if ZM_MEM_MAPPED map_fd = -1; - snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); + snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); #else // ZM_MEM_MAPPED shm_id = 0; #endif // ZM_MEM_MAPPED mem_size = 0; mem_ptr = nullptr; - last_event = 0; + last_event_id = 0; last_state = IDLE; last_connect_time = 0; @@ -140,7 +148,7 @@ bool Monitor::MonitorLink::connect() { mem_size = sizeof(SharedData) + sizeof(TriggerData); - Debug(1, "link.mem.size=%d", mem_size); + Debug(1, "link.mem.size=%jd", mem_size); #if ZM_MEM_MAPPED map_fd = open(mem_file, O_RDWR, (mode_t)0600); if ( map_fd < 0 ) { @@ -167,26 +175,26 @@ bool Monitor::MonitorLink::connect() { disconnect(); return false; } else if ( map_stat.st_size < mem_size ) { - Error("Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size); + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); disconnect(); return false; } mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); if ( mem_ptr == MAP_FAILED ) { - Error("Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno)); + Error("Can't map file %s (%jd bytes) to memory: %s", mem_file, mem_size, strerror(errno)); disconnect(); return false; } #else // ZM_MEM_MAPPED - shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 ); + shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); if ( shm_id < 0 ) { - Debug(3, "Can't shmget link memory: %s", strerror(errno ); + Debug(3, "Can't shmget link memory: %s", strerror(errno)); connected = false; return false; } mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); - if ( mem_ptr < (void *)0 ) { + if ((int)mem_ptr == -1) { Debug(3, "Can't shmat link memory: %s", strerror(errno)); connected = false; return false; @@ -203,13 +211,13 @@ bool Monitor::MonitorLink::connect() { } last_state = shared_data->state; - last_event = shared_data->last_event; + last_event_id = shared_data->last_event_id; connected = true; return true; } return false; -} +} // end bool Monitor::MonitorLink::connect() bool Monitor::MonitorLink::disconnect() { if ( connected ) { @@ -233,190 +241,180 @@ bool Monitor::MonitorLink::disconnect() { shm_id = 0; - if ( shm_data.shm_nattch <= 1 ) { - if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) { - Debug( 3, "Can't shmctl: %s", strerror(errno) ); - return( false ); + if (shm_data.shm_nattch <= 1) { + if (shmctl(shm_id, IPC_RMID, 0) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; } } - if ( shmdt( mem_ptr ) < 0 ) { - Debug( 3, "Can't shmdt: %s", strerror(errno) ); - return( false ); + if (shmdt(mem_ptr) < 0) { + Debug(3, "Can't shmdt: %s", strerror(errno)); + return false; } #endif // ZM_MEM_MAPPED mem_size = 0; mem_ptr = nullptr; } - return( true ); + return true; } bool Monitor::MonitorLink::isAlarmed() { - if ( !connected ) { - return( false ); + if (!connected) { + return false; } return( shared_data->state == ALARM ); } bool Monitor::MonitorLink::inAlarm() { - if ( !connected ) { - return( false ); + if (!connected) { + return false; } return( shared_data->state == ALARM || shared_data->state == ALERT ); } bool Monitor::MonitorLink::hasAlarmed() { - if ( shared_data->state == ALARM ) { + if (shared_data->state == ALARM) { return true; - } else if ( shared_data->last_event != last_event ) { - last_event = shared_data->last_event; } + last_event_id = shared_data->last_event_id; return false; } -Monitor::Monitor( - unsigned int p_id, - const char *p_name, - const unsigned int p_server_id, - const unsigned int p_storage_id, - int p_function, - bool p_enabled, - const char *p_linked_monitors, - Camera *p_camera, - int p_orientation, - unsigned int p_deinterlacing, - const std::string &p_decoder_hwaccel_name, - const std::string &p_decoder_hwaccel_device, - int p_savejpegs, - VideoWriter p_videowriter, - std::string p_encoderparams, - bool p_record_audio, - const char *p_event_prefix, - const char *p_label_format, - const Coord &p_label_coord, - int p_label_size, - int p_image_buffer_count, - int p_warmup_count, - int p_pre_event_count, - int p_post_event_count, - int p_stream_replay_buffer, - int p_alarm_frame_count, - int p_section_length, - int p_min_section_length, - int p_frame_skip, - int p_motion_frame_skip, - double p_capture_max_fps, - double p_analysis_fps, - unsigned int p_analysis_update_delay, - int p_capture_delay, - int p_alarm_capture_delay, - int p_fps_report_interval, - int p_ref_blend_perc, - int p_alarm_ref_blend_perc, - bool p_track_motion, - int p_signal_check_points, - Rgb p_signal_check_colour, - bool p_embed_exif, - Purpose p_purpose, - int p_n_zones, - Zone *p_zones[] -) : id( p_id ), - server_id( p_server_id ), - storage_id( p_storage_id ), - function( (Function)p_function ), - enabled( p_enabled ), - width( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Height():p_camera->Width() ), - height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), - orientation( (Orientation)p_orientation ), - deinterlacing( p_deinterlacing ), - decoder_hwaccel_name(p_decoder_hwaccel_name), - decoder_hwaccel_device(p_decoder_hwaccel_device), - savejpegs( p_savejpegs ), - videowriter( p_videowriter ), - encoderparams( p_encoderparams ), - record_audio( p_record_audio ), - label_coord( p_label_coord ), - label_size( p_label_size ), - image_buffer_count( p_image_buffer_count ), - warmup_count( p_warmup_count ), - pre_event_count( p_pre_event_count ), - post_event_count( p_post_event_count ), - video_buffer_duration({0}), - stream_replay_buffer( p_stream_replay_buffer ), - section_length( p_section_length ), - min_section_length( p_min_section_length ), - frame_skip( p_frame_skip ), - motion_frame_skip( p_motion_frame_skip ), - capture_max_fps( p_capture_max_fps ), - analysis_fps( p_analysis_fps ), - analysis_update_delay( p_analysis_update_delay ), - capture_delay( p_capture_delay ), - alarm_capture_delay( p_alarm_capture_delay ), - alarm_frame_count( p_alarm_frame_count ), - fps_report_interval( p_fps_report_interval ), - ref_blend_perc( p_ref_blend_perc ), - alarm_ref_blend_perc( p_alarm_ref_blend_perc ), - track_motion( p_track_motion ), - signal_check_points(p_signal_check_points), - signal_check_colour( p_signal_check_colour ), - embed_exif( p_embed_exif ), - delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ), - ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ), - purpose( p_purpose ), +Monitor::Monitor() + : id(0), + name(""), + server_id(0), + storage_id(0), + type(LOCAL), + function(NONE), + enabled(0), + decoding_enabled(0), + //protocol + //method + //options + //host + //port + //user + //pass + //path + //device + palette(0), + channel(0), + format(0), + + width(0), + height(0), + //v4l_multi_buffer + //v4l_captures_per_frame + orientation(ROTATE_0), + deinterlacing(0), + deinterlacing_value(0), + decoder_hwaccel_name(""), + decoder_hwaccel_device(""), + videoRecording(0), + rtsp_describe(0), + + savejpegs(0), + colours(0), + videowriter(DISABLED), + encoderparams(""), + output_codec(0), + encoder(""), + output_container(""), + imagePixFormat(AV_PIX_FMT_NONE), + record_audio(0), +//event_prefix +//label_format + label_coord(Coord(0,0)), + label_size(0), + image_buffer_count(0), + max_image_buffer_count(0), + warmup_count(0), + pre_event_count(0), + post_event_count(0), + stream_replay_buffer(0), + section_length(0), + min_section_length(0), + adaptive_skip(false), + frame_skip(0), + motion_frame_skip(0), + analysis_fps_limit(0), + analysis_update_delay(0), + capture_delay(0), + alarm_capture_delay(0), + alarm_frame_count(0), + alert_to_alarm_frame_count(0), + fps_report_interval(0), + ref_blend_perc(0), + alarm_ref_blend_perc(0), + track_motion(0), + signal_check_points(0), + signal_check_colour(0), + embed_exif(0), + rtsp_server(0), + rtsp_streamname(""), + importance(0), + capture_max_fps(0), + purpose(QUERY), + last_camera_bytes(0), + event_count(0), + image_count(0), + last_capture_image_count(0), + analysis_image_count(0), + motion_frame_count(0), + last_motion_frame_count(0), + ready_count(0), + first_alarm_count(0), + last_alarm_count(0), + last_signal(false), + last_section_mod(0), + buffer_count(0), + state(IDLE), + start_time(0), + last_fps_time(0), + last_analysis_fps_time(0), + auto_resume_time(0), last_motion_score(0), - camera( p_camera ), - n_zones( p_n_zones ), - zones( p_zones ), - timestamps( nullptr ), - images( nullptr ), - privacy_bitmask( nullptr ), - event_delete_thread(nullptr) + event_close_mode(CLOSE_IDLE), +#if ZM_MEM_MAPPED + map_fd(-1), + mem_file(""), +#else // ZM_MEM_MAPPED + shm_id(-1), +#endif // ZM_MEM_MAPPED + mem_size(0), + mem_ptr(nullptr), + shared_data(nullptr), + trigger_data(nullptr), + video_store_data(nullptr), + shared_timestamps(nullptr), + shared_images(nullptr), + video_stream_id(-1), + audio_stream_id(-1), + video_fifo(nullptr), + audio_fifo(nullptr), + camera(nullptr), + event(nullptr), + storage(nullptr), + videoStore(nullptr), + analysis_it(nullptr), + analysis_thread(nullptr), + decoder_it(nullptr), + decoder(nullptr), + dest_frame(nullptr), + convert_context(nullptr), + //zones(nullptr), + privacy_bitmask(nullptr), + n_linked_monitors(0), + linked_monitors(nullptr), + red_val(0), + green_val(0), + blue_val(0), + grayscale_val(0), + colour_val(0) { - if (analysis_fps > 0.0) { - uint64_t usec = round(1000000*pre_event_count/analysis_fps); - video_buffer_duration.tv_sec = usec/1000000; - video_buffer_duration.tv_usec = usec % 1000000; - } - - strncpy(name, p_name, sizeof(name)-1); - - strncpy(event_prefix, p_event_prefix, sizeof(event_prefix)-1); - strncpy(label_format, p_label_format, sizeof(label_format)-1); - Debug(1, "encoder params %s", encoderparams.c_str()); - - // Change \n to actual line feeds - char *token_ptr = label_format; - const char *token_string = "\n"; - while ( ( token_ptr = strstr(token_ptr, token_string) ) ) { - if ( *(token_ptr+1) ) { - *token_ptr = '\n'; - token_ptr++; - strcpy(token_ptr, token_ptr+1); - } else { - *token_ptr = '\0'; - break; - } - } - - /* Parse encoder parameters */ - ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); - - fps = 0.0; - last_camera_bytes = 0; - event_count = 0; - image_count = 0; - ready_count = warmup_count; - first_alarm_count = 0; - last_alarm_count = 0; - state = IDLE; - - if ( alarm_frame_count < 1 ) - alarm_frame_count = 1; - else if ( alarm_frame_count > MAX_PRE_ALARM_FRAMES ) - alarm_frame_count = MAX_PRE_ALARM_FRAMES; - - auto_resume_time = 0; if ( strcmp(config.event_close_mode, "time") == 0 ) event_close_mode = CLOSE_TIME; @@ -425,48 +423,599 @@ Monitor::Monitor( else event_close_mode = CLOSE_IDLE; - Debug(1, "monitor purpose=%d", purpose); + event = 0; + last_section_mod = 0; + + adaptive_skip = true; + + videoStore = nullptr; +} // Monitor::Monitor + +/* + std::string load_monitor_sql = + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, " + "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," + "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings + "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " + "SaveJPEGs, VideoWriter, EncoderParameters, + "OutputCodec, Encoder, OutputContainer, RecordAudio, " + "Brightness, Contrast, Hue, Colour, " + "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," + "ImageBufferCount, `MaxImageBufferCount`, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " + "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " + "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," + "`RTSPServer`,`RTSPStreamName`, + "SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; +*/ + +void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { + purpose = p; + int col = 0; + + id = atoi(dbrow[col]); col++; + name = dbrow[col]; col++; + server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + + storage_id = atoi(dbrow[col]); col++; + if (storage) delete storage; + storage = new Storage(storage_id); + + if ( ! strcmp(dbrow[col], "Local") ) { + type = LOCAL; + } else if ( ! strcmp(dbrow[col], "Ffmpeg") ) { + type = FFMPEG; + } else if ( ! strcmp(dbrow[col], "Remote") ) { + type = REMOTE; + } else if ( ! strcmp(dbrow[col], "File") ) { + type = FILE; + } else if ( ! strcmp(dbrow[col], "NVSocket") ) { + type = NVSOCKET; + } else if ( ! strcmp(dbrow[col], "Libvlc") ) { + type = LIBVLC; + } else if ( ! strcmp(dbrow[col], "cURL") ) { + type = CURL; + } else if ( ! strcmp(dbrow[col], "VNC") ) { + type = VNC; + } else { + Fatal("Bogus monitor type '%s' for monitor %d", dbrow[col], id); + } + Debug(1, "Have camera type %s", CameraType_Strings[type].c_str()); + col++; + function = (Function)atoi(dbrow[col]); col++; + enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + + ReloadLinkedMonitors(dbrow[col]); col++; + + /* "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," */ + analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++; + analysis_update_delay = strtoul(dbrow[col++], nullptr, 0); + capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++; + alarm_capture_delay = (dbrow[col] && atof(dbrow[col])>0.0)?int(DT_PREC_6/atof(dbrow[col])):0; col++; + + /* "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings */ + device = dbrow[col] ? dbrow[col] : ""; col++; + channel = atoi(dbrow[col]); col++; + format = atoi(dbrow[col]); col++; + v4l_multi_buffer = config.v4l_multi_buffer; + if ( dbrow[col] ) { + if (*dbrow[col] == '0' ) { + v4l_multi_buffer = false; + } else if ( *dbrow[col] == '1' ) { + v4l_multi_buffer = true; + } + } + col++; + + v4l_captures_per_frame = 0; + if ( dbrow[col] ) { + v4l_captures_per_frame = atoi(dbrow[col]); + } else { + v4l_captures_per_frame = config.captures_per_frame; + } + col++; + + /* "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, " */ + protocol = dbrow[col] ? dbrow[col] : ""; col++; + method = dbrow[col] ? dbrow[col] : ""; col++; + options = dbrow[col] ? dbrow[col] : ""; col++; + user = dbrow[col] ? dbrow[col] : ""; col++; + pass = dbrow[col] ? dbrow[col] : ""; col++; + host = dbrow[col] ? dbrow[col] : ""; col++; + port = dbrow[col] ? dbrow[col] : ""; col++; + path = dbrow[col] ? dbrow[col] : ""; col++; + second_path = dbrow[col] ? dbrow[col] : ""; col++; + camera_width = atoi(dbrow[col]); col++; + camera_height = atoi(dbrow[col]); col++; + colours = atoi(dbrow[col]); col++; + palette = atoi(dbrow[col]); col++; + orientation = (Orientation)atoi(dbrow[col]); col++; + width = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_height : camera_width; + height = (orientation==ROTATE_90||orientation==ROTATE_270) ? camera_width : camera_height; + deinterlacing = atoi(dbrow[col]); col++; + deinterlacing_value = deinterlacing & 0xff; + +/*"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " */ + decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++; + decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++; + rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + + +/* "`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " */ + savejpegs = atoi(dbrow[col]); col++; + videowriter = (VideoWriter)atoi(dbrow[col]); col++; + encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + +/*"`OutputCodec`, `Encoder`, `OutputContainer`, " */ + output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + encoder = dbrow[col] ? dbrow[col] : ""; col++; + output_container = dbrow[col] ? dbrow[col] : ""; col++; + record_audio = (*dbrow[col] != '0'); col++; + + /* "Brightness, Contrast, Hue, Colour, " */ + brightness = atoi(dbrow[col]); col++; + contrast = atoi(dbrow[col]); col++; + hue = atoi(dbrow[col]); col++; + colour = atoi(dbrow[col]); col++; + + /* "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," */ + event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + label_format = dbrow[col] ? ReplaceAll(dbrow[col], "\\n", "\n") : ""; col++; + label_coord = Coord(atoi(dbrow[col]), atoi(dbrow[col+1])); col += 2; + label_size = atoi(dbrow[col]); col++; + + /* "ImageBufferCount, `MaxImageBufferCount`, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " */ + image_buffer_count = atoi(dbrow[col]); col++; + max_image_buffer_count = atoi(dbrow[col]); col++; + warmup_count = atoi(dbrow[col]); col++; + pre_event_count = atoi(dbrow[col]); col++; + packetqueue.setPreEventVideoPackets(pre_event_count); + packetqueue.setMaxVideoPackets(max_image_buffer_count); + packetqueue.setKeepKeyframes(videowriter == PASSTHROUGH); + post_event_count = atoi(dbrow[col]); col++; + stream_replay_buffer = atoi(dbrow[col]); col++; + alarm_frame_count = atoi(dbrow[col]); col++; + if (alarm_frame_count < 1) alarm_frame_count = 1; + else if (alarm_frame_count > MAX_PRE_ALARM_FRAMES) alarm_frame_count = MAX_PRE_ALARM_FRAMES; + + /* "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " */ + section_length = atoi(dbrow[col]); col++; + min_section_length = atoi(dbrow[col]); col++; + frame_skip = atoi(dbrow[col]); col++; + motion_frame_skip = atoi(dbrow[col]); col++; + + /* "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," */ + fps_report_interval = atoi(dbrow[col]); col++; + ref_blend_perc = atoi(dbrow[col]); col++; + alarm_ref_blend_perc = atoi(dbrow[col]); col++; + track_motion = atoi(dbrow[col]); col++; + embed_exif = (*dbrow[col] != '0'); col++; + + /* "`RTSPServer`,`RTSPStreamName`, */ + rtsp_server = (*dbrow[col] != '0'); col++; + rtsp_streamname = dbrow[col]; col++; + + /*"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; */ + signal_check_points = atoi(dbrow[col]); col++; + signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++; + + colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ + colour_val = rgb_convert(colour_val, palette); + red_val = RED_VAL_BGRA(signal_check_colour); + green_val = GREEN_VAL_BGRA(signal_check_colour); + blue_val = BLUE_VAL_BGRA(signal_check_colour); + grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */ + + importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++; + + // How many frames we need to have before we start analysing + ready_count = std::max(warmup_count, pre_event_count); + + last_alarm_count = 0; + state = IDLE; + last_signal = true; // Defaulting to having signal so that we don't get a signal change on the first frame. + // Instead initial failure to capture will cause a loss of signal change which I think makes more sense. + uint64_t image_size = width * height * colours; + + if ( strcmp(config.event_close_mode, "time") == 0 ) + event_close_mode = CLOSE_TIME; + else if ( strcmp(config.event_close_mode, "alarm") == 0 ) + event_close_mode = CLOSE_ALARM; + else + event_close_mode = CLOSE_IDLE; mem_size = sizeof(SharedData) + sizeof(TriggerData) + sizeof(VideoStoreData) //Information to pass back to the capture process - + (image_buffer_count*sizeof(struct timeval)) - + (image_buffer_count*camera->ImageSize()) + + (image_buffer_count * sizeof(struct timeval)) + + (image_buffer_count * image_size) + 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */ - Debug(1, "mem.size(%d) SharedData=%d TriggerData=%d VideoStoreData=%d timestamps=%d images=%dx%d = %" PRId64 " total=%" PRId64, - sizeof(mem_size), - sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData), - (image_buffer_count*sizeof(struct timeval)), - image_buffer_count, camera->ImageSize(), (image_buffer_count*camera->ImageSize()), - mem_size); - mem_ptr = nullptr; + Debug(1, + "mem.size(%zu) SharedData=%zu TriggerData=%zu VideoStoreData=%zu timestamps=%zu images=%dx%" PRIi64 " = %" PRId64 " total=%jd", + sizeof(mem_size), + sizeof(SharedData), + sizeof(TriggerData), + sizeof(VideoStoreData), + (image_buffer_count * sizeof(struct timeval)), + image_buffer_count, + image_size, + (image_buffer_count * image_size), + mem_size); - storage = new Storage(storage_id); - Debug(1, "Storage path: %s", storage->Path()); // Should maybe store this for later use - char monitor_dir[PATH_MAX]; - snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id); + std::string monitor_dir = stringtf("%s/%u", storage->Path(), id); - if ( purpose == CAPTURE ) { - if ( mkdir(monitor_dir, 0755) && ( errno != EEXIST ) ) { - Error("Can't mkdir %s: %s", monitor_dir, strerror(errno)); + if ( purpose != QUERY ) { + LoadCamera(); + ReloadZones(); + + if ( mkdir(monitor_dir.c_str(), 0755) && ( errno != EEXIST ) ) { + Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno)); } - if ( !this->connect() ) { - Error("unable to connect, but doing capture"); - exit(-1); - } + // Do this here to save a few cycles with all the comparisons + decoding_enabled = !( + ( function == RECORD or function == NODECT ) + and + ( savejpegs == 0 ) + and + ( videowriter == PASSTHROUGH ) + and + !decoding_enabled + ); + Debug(1, "Decoding enabled: %d", decoding_enabled); + if ( config.record_diag_images ) { + if ( config.record_diag_images_fifo ) { + diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); + diag_path_delta = stringtf("%s/diagpipe-d-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); + Fifo::fifo_create_if_missing(diag_path_ref.c_str()); + Fifo::fifo_create_if_missing(diag_path_delta.c_str()); + } else { + diag_path_ref = stringtf("%s/%d/diag-r.jpg", storage->Path(), id); + diag_path_delta = stringtf("%s/%d/diag-d.jpg", storage->Path(), id); + } + } + } // end if purpose + + Debug(1, "Loaded monitor %d(%s), %zu zones", id, name.c_str(), zones.size()); +} // Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) + +void Monitor::LoadCamera() { + if (camera) + return; + + switch (type) { + case LOCAL: { +#if ZM_HAS_V4L + int extras = (deinterlacing >> 24) & 0xff; + + camera = ZM::make_unique(this, + device, + channel, + format, + v4l_multi_buffer, + v4l_captures_per_frame, + method, + camera_width, + camera_height, + colours, + palette, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio, + extras + ); +#else + Fatal("Not compiled with local v4l camera support"); +#endif + break; + } + case REMOTE: { + if (protocol == "http") { + camera = ZM::make_unique(this, + method, + host, + port, + path, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + } +#if HAVE_LIBAVFORMAT + else if (protocol == "rtsp") { + camera = ZM::make_unique(this, + method, + host, // Host + port, // Port + path, // Path + camera_width, + camera_height, + rtsp_describe, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + } +#endif // HAVE_LIBAVFORMAT + else { + Error("Unexpected remote camera protocol '%s'", protocol.c_str()); + } + break; + } + case FILE: { + camera = ZM::make_unique(this, + path.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + break; + } +#if HAVE_LIBAVFORMAT + case FFMPEG: { + camera = ZM::make_unique(this, + path, + second_path, + method, + options, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio, + decoder_hwaccel_name, + decoder_hwaccel_device + ); + break; + } +#endif // HAVE_LIBAVFORMAT + case NVSOCKET: { + camera = ZM::make_unique(this, + host.c_str(), + port.c_str(), + path.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); + break; + } + case LIBVLC: { +#if HAVE_LIBVLC + camera = ZM::make_unique(this, + path.c_str(), + method, + options, + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBVLC + Error("You must have vlc libraries installed to use vlc cameras for monitor %d", id); +#endif // HAVE_LIBVLC + break; + } + case CURL: { +#if HAVE_LIBCURL + camera = ZM::make_unique(this, + path.c_str(), + user.c_str(), + pass.c_str(), + camera_width, + camera_height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBCURL + Error("You must have libcurl installed to use ffmpeg cameras for monitor %d", id); +#endif // HAVE_LIBCURL + break; + } + case VNC: { +#if HAVE_LIBVNC + camera = ZM::make_unique(this, + host.c_str(), + port.c_str(), + user.c_str(), + pass.c_str(), + width, + height, + colours, + brightness, + contrast, + hue, + colour, + purpose == CAPTURE, + record_audio + ); +#else // HAVE_LIBVNC + Fatal("You must have libvnc installed to use VNC cameras for monitor id %d", id); +#endif // HAVE_LIBVNC + break; + } + default: { + Fatal("Tried to load unsupported camera type %d for monitor %u", int(type), id); + break; + } + } +} + +std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) { + std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", p_id); + + zmDbRow dbrow; + if ( !dbrow.fetch(sql.c_str()) ) { + Error("Can't use query result: %s", mysql_error(&dbconn)); + return nullptr; + } + + std::shared_ptr monitor = std::make_shared(); + monitor->Load(dbrow.mysql_row(), load_zones, purpose); + + return monitor; +} + +bool Monitor::connect() { + + if (mem_ptr != nullptr) { + Warning("Already connected. Please call disconnect first."); + } + Debug(3, "Connecting to monitor. Purpose is %d", purpose); +#if ZM_MEM_MAPPED + snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); + if (purpose != CAPTURE) { + map_fd = open(mem_file, O_RDWR); + } else { + map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0660); + } + + if (map_fd < 0) { + Error("Can't open memory map file %s: %s", mem_file, strerror(errno)); + return false; + } else { + Debug(3, "Success opening mmap file at (%s)", mem_file); + } + + struct stat map_stat; + if (fstat(map_fd, &map_stat) < 0) { + Error("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno)); + close(map_fd); + map_fd = -1; + return false; + } + + if (map_stat.st_size != mem_size) { + if (purpose == CAPTURE) { + // Allocate the size + if (ftruncate(map_fd, mem_size) < 0) { + Error("Can't extend memory map file %s to %jd bytes: %s", mem_file, mem_size, strerror(errno)); + close(map_fd); + map_fd = -1; + return false; + } + } else if (map_stat.st_size == 0) { + Error("Got empty memory map file size %ld, is the zmc process for this monitor running?", map_stat.st_size); + close(map_fd); + map_fd = -1; + return false; + } else { + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); + close(map_fd); + map_fd = -1; + return false; + } + } // end if map_stat.st_size != mem_size + + Debug(3, "MMap file size is %ld", map_stat.st_size); +#ifdef MAP_LOCKED + mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); + if (mem_ptr == MAP_FAILED) { + if (errno == EAGAIN) { + Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file, mem_size); +#endif + mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); + Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file, mem_size); +#ifdef MAP_LOCKED + } else { + Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno)); + } + } +#endif + if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) { + Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno); + close(map_fd); + map_fd = -1; + mem_ptr = nullptr; + return false; + } +#else // ZM_MEM_MAPPED + shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700); + if (shm_id < 0) { + Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno)); + } + mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); + if ((int)mem_ptr == -1) { + Fatal("Can't shmat: %s", strerror(errno)); + } +#endif // ZM_MEM_MAPPED + + shared_data = (SharedData *)mem_ptr; + trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); + video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); + shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); + shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); + + if (((unsigned long)shared_images % 64) != 0) { + /* Align images buffer to nearest 64 byte boundary */ + Debug(3, "Aligning shared memory images to the next 64 byte boundary"); + shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); + } + if (!camera) LoadCamera(); + + Debug(3, "Allocating %d image buffers", image_buffer_count); + image_buffer = new ZMPacket[image_buffer_count]; + for (int32_t i = 0; i < image_buffer_count; i++) { + image_buffer[i].image_index = i; + image_buffer[i].timestamp = &(shared_timestamps[i]); + image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); + image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ + } + + if (purpose == CAPTURE) { memset(mem_ptr, 0, mem_size); shared_data->size = sizeof(SharedData); shared_data->active = enabled; shared_data->signal = false; + shared_data->capture_fps = 0.0; + shared_data->analysis_fps = 0.0; shared_data->state = IDLE; shared_data->last_write_index = image_buffer_count; shared_data->last_read_index = image_buffer_count; shared_data->last_write_time = 0; - shared_data->last_event = 0; + shared_data->last_event_id = 0; shared_data->action = (Action)0; shared_data->brightness = -1; shared_data->hue = -1; @@ -477,343 +1026,159 @@ Monitor::Monitor( shared_data->format = camera->SubpixelOrder(); shared_data->imagesize = camera->ImageSize(); shared_data->alarm_cause[0] = 0; + shared_data->video_fifo_path[0] = 0; + shared_data->audio_fifo_path[0] = 0; + shared_data->last_frame_score = 0; + shared_data->audio_frequency = -1; + shared_data->audio_channels = -1; trigger_data->size = sizeof(TriggerData); - trigger_data->trigger_state = TRIGGER_CANCEL; + trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL; trigger_data->trigger_score = 0; trigger_data->trigger_cause[0] = 0; trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; - shared_data->valid = true; - video_store_data->recording = (struct timeval){0}; + video_store_data->recording = {}; + // Uh, why nothing? Why not nullptr? snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); video_store_data->size = sizeof(VideoStoreData); - //video_store_data->frameNumber = 0; - } else if ( purpose == ANALYSIS ) { - if ( ! (this->connect() && mem_ptr && shared_data->valid) ) { - Error("Shared data not initialised by capture daemon for monitor %s", name); - exit(-1); - } - shared_data->state = IDLE; - shared_data->last_read_time = 0; - shared_data->alarm_x = -1; - shared_data->alarm_y = -1; + shared_data->valid = true; + } else if ( !shared_data->valid ) { + Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); + return false; } - start_time = last_fps_time = time( 0 ); + // We set these here because otherwise the first fps calc is meaningless + struct timeval now; + gettimeofday(&now, nullptr); + double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); + last_fps_time = now_double; + last_analysis_fps_time = now_double; - event = 0; + Debug(3, "Success connecting"); + return true; +} // Monitor::connect - Debug(1, "Monitor %s has function %d,\n" - "label format = '%s', label X = %d, label Y = %d, label size = %d,\n" - "image buffer count = %d, warmup count = %d, pre-event count = %d, post-event count = %d, alarm frame count = %d,\n" - "fps report interval = %d, ref blend percentage = %d, alarm ref blend percentage = %d, track motion = %d", - name, function, - label_format, label_coord.X(), label_coord.Y(), label_size, - image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, - fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion ); +bool Monitor::disconnect() { + if (mem_ptr == nullptr) { + Debug(1, "Already disconnected"); + return true; + } - //Set video recording flag for event start constructor and easy reference in code - videoRecording = ((GetOptVideoWriter() == H264PASSTHROUGH) && camera->SupportsNativeVideo()); - - n_linked_monitors = 0; - linked_monitors = nullptr; - - if ( purpose == ANALYSIS ) { - while( - ( shared_data->last_write_index == (unsigned int)image_buffer_count ) - && - ( shared_data->last_write_time == 0 ) - && - ( !zm_terminate ) - ) { - Debug(1, "Waiting for capture daemon last_write_index(%d), last_write_time(%d)", - shared_data->last_write_index, shared_data->last_write_time ); - sleep(1); - } - - ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(), - image_buffer[shared_data->last_write_index].image->Buffer(), camera->ImageSize()); - adaptive_skip = true; - - ReloadLinkedMonitors(p_linked_monitors); - - if ( config.record_diag_images ) { - diag_path_r = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-r.jpg" : "%s/%d/diag-r.jpg", storage->Path(), id); - diag_path_d = stringtf(config.record_diag_images_fifo ? "%s/%d/diagpipe-d.jpg" : "%s/%d/diag-d.jpg", storage->Path(), id); - if ( config.record_diag_images_fifo ) { - FifoStream::fifo_create_if_missing(diag_path_r.c_str()); - FifoStream::fifo_create_if_missing(diag_path_d.c_str()); - } - } - } // end if purpose == ANALYSIS -} // Monitor::Monitor - -bool Monitor::connect() { - Debug(3, "Connecting to monitor. Purpose is %d", purpose ); #if ZM_MEM_MAPPED - snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); - map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0600); - if ( map_fd < 0 ) { - Error("Can't open memory map file %s, probably not enough space free: %s", mem_file, strerror(errno)); - return false; - } else { - Debug(3, "Success opening mmap file at (%s)", mem_file); - } + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); + if (map_fd >= 0) close(map_fd); - struct stat map_stat; - if ( fstat(map_fd, &map_stat) < 0 ) { - Error("Can't stat memory map file %s: %s, is the zmc process for this monitor running?", mem_file, strerror(errno)); - close(map_fd); - return false; - } + map_fd = -1; + mem_ptr = nullptr; + shared_data = nullptr; - if ( map_stat.st_size != mem_size ) { - if ( purpose == CAPTURE ) { - // Allocate the size - if ( ftruncate(map_fd, mem_size) < 0 ) { - Fatal("Can't extend memory map file %s to %d bytes: %s", mem_file, mem_size, strerror(errno)); - } - } else if ( map_stat.st_size == 0 ) { - Error("Got empty memory map file size %ld, is the zmc process for this monitor running?", map_stat.st_size, mem_size); - close(map_fd); - map_fd = -1; - return false; - } else { - Error("Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size); - close(map_fd); - map_fd = -1; - return false; - } - } - - Debug(3, "MMap file size is %ld", map_stat.st_size); -#ifdef MAP_LOCKED - mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0); - if ( mem_ptr == MAP_FAILED ) { - if ( errno == EAGAIN ) { - Debug(1, "Unable to map file %s (%d bytes) to locked memory, trying unlocked", mem_file, mem_size); -#endif - mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); - Debug(1, "Mapped file %s (%d bytes) to unlocked memory", mem_file, mem_size); -#ifdef MAP_LOCKED - } else { - Error("Unable to map file %s (%d bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno)); - } - } -#endif - if ( mem_ptr == MAP_FAILED ) - Fatal("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno); - if ( mem_ptr == nullptr ) { - Error("mmap gave a NULL address:"); - } else { - Debug(3, "mmapped to %p", mem_ptr); + if (purpose == CAPTURE and (unlink(mem_file) < 0) ) { + Warning("Can't unlink '%s': %s", mem_file, strerror(errno)); } #else // ZM_MEM_MAPPED - shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700); - if ( shm_id < 0 ) { - Fatal("Can't shmget, probably not enough shared memory space free: %s", strerror(errno)); + struct shmid_ds shm_data; + if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; } - mem_ptr = (unsigned char *)shmat(shm_id, 0, 0); - if ( mem_ptr < (void *)0 ) { - Fatal("Can't shmat: %s", strerror(errno)); + + shm_id = 0; + + if ((shm_data.shm_nattch <= 1) and (shmctl(shm_id, IPC_RMID, 0) < 0)) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; + } + + if (shmdt(mem_ptr) < 0) { + Debug(3, "Can't shmdt: %s", strerror(errno)); + return false; } #endif // ZM_MEM_MAPPED - shared_data = (SharedData *)mem_ptr; - trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); - struct timeval *shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); - unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); - if ( ((unsigned long)shared_images % 64) != 0 ) { - /* Align images buffer to nearest 64 byte boundary */ - Debug(3,"Aligning shared memory images to the next 64 byte boundary"); - shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); + if (image_buffer) { + for ( int32_t i = 0; i < image_buffer_count; i++ ) { + // We delete the image because it is an object pointing to space that won't be free'd. + delete image_buffer[i].image; + image_buffer[i].image = nullptr; + // We don't delete the timestamp because it is just a pointer to shared mem. + image_buffer[i].timestamp = nullptr; + } + delete[] image_buffer; + image_buffer = nullptr; } - Debug(3, "Allocating %d image buffers", image_buffer_count); - image_buffer = new Snapshot[image_buffer_count]; - for ( int i = 0; i < image_buffer_count; i++ ) { - image_buffer[i].timestamp = &(shared_timestamps[i]); - image_buffer[i].image = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); - image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ - } - if ( (deinterlacing & 0xff) == 4 ) { - /* Four field motion adaptive deinterlacing in use */ - /* Allocate a buffer for the next image */ - next_buffer.image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); - next_buffer.timestamp = new struct timeval; - } - if ( purpose == ANALYSIS ) { - if ( analysis_fps ) { - // Size of pre event buffer must be greater than pre_event_count - // if alarm_frame_count > 1, because in this case the buffer contains - // alarmed images that must be discarded when event is created - pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; - pre_event_buffer = new Snapshot[pre_event_buffer_count]; - for ( int i = 0; i < pre_event_buffer_count; i++ ) { - pre_event_buffer[i].timestamp = new struct timeval; - *pre_event_buffer[i].timestamp = {0,0}; - pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); - } - } // end if max_analysis_fps - timestamps = new struct timeval *[pre_event_count]; - images = new Image *[pre_event_count]; - last_signal = shared_data->signal; - } // end if purpose == ANALYSIS -Debug(3, "Success connecting"); return true; -} // end Monitor::connect +} // end bool Monitor::disconnect() Monitor::~Monitor() { - if ( n_linked_monitors ) { - for( int i = 0; i < n_linked_monitors; i++ ) { + Close(); + + if (mem_ptr != nullptr) { + if (purpose != QUERY) { + shared_data->state = state = IDLE; + shared_data->last_read_index = image_buffer_count; + shared_data->last_read_time = 0; + shared_data->valid = false; + memset(mem_ptr, 0, mem_size); + } // end if purpose != query + disconnect(); + } // end if mem_ptr + + // Will be free by packetqueue destructor + analysis_it = nullptr; + decoder_it = nullptr; + + delete storage; + if (n_linked_monitors) { + for ( int i=0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; linked_monitors = nullptr; } - if ( timestamps ) { - delete[] timestamps; - timestamps = nullptr; + + if (video_fifo) delete video_fifo; + if (audio_fifo) delete audio_fifo; + if (dest_frame) av_frame_free(&dest_frame); + if (convert_context) { + sws_freeContext(convert_context); + convert_context = nullptr; } - if ( images ) { - delete[] images; - images = nullptr; - } - if ( privacy_bitmask ) { - delete[] privacy_bitmask; - privacy_bitmask = nullptr; - } - if ( mem_ptr ) { - if ( event ) { - Info( "%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name, image_count, event->Id() ); - closeEvent(); +} // end Monitor::~Monitor() - // closeEvent may start another thread to close the event, so wait for it to finish - if ( event_delete_thread ) { - event_delete_thread->join(); - delete event_delete_thread; - event_delete_thread = nullptr; - } - } - - if ( (deinterlacing & 0xff) == 4) { - delete next_buffer.image; - delete next_buffer.timestamp; - } - for ( int i = 0; i < image_buffer_count; i++ ) { - delete image_buffer[i].image; - } - delete[] image_buffer; - } // end if mem_ptr - - for ( int i = 0; i < n_zones; i++ ) { - delete zones[i]; - } - delete[] zones; - - delete camera; - delete storage; - - if ( mem_ptr ) { - if ( purpose == ANALYSIS ) { - shared_data->state = state = IDLE; - shared_data->last_read_index = image_buffer_count; - shared_data->last_read_time = 0; - - if ( analysis_fps ) { - for ( int i = 0; i < pre_event_buffer_count; i++ ) { - delete pre_event_buffer[i].image; - delete pre_event_buffer[i].timestamp; - } - delete[] pre_event_buffer; - } - if ( Event::PreAlarmCount() ) - Event::EmptyPreAlarmFrames(); - } else if ( purpose == CAPTURE ) { - shared_data->valid = false; - memset(mem_ptr, 0, mem_size); - } - -#if ZM_MEM_MAPPED - if ( msync(mem_ptr, mem_size, MS_SYNC) < 0 ) - Error("Can't msync: %s", strerror(errno)); - if ( munmap(mem_ptr, mem_size) < 0 ) - Fatal("Can't munmap: %s", strerror(errno)); - close( map_fd ); - - if ( purpose == CAPTURE ) { - // How about we store this in the object on instantiation so that we don't have to do this again. - char mmap_path[PATH_MAX] = ""; - snprintf(mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); - - if ( unlink(mmap_path) < 0 ) { - Warning("Can't unlink '%s': %s", mmap_path, strerror(errno)); - } - } -#else // ZM_MEM_MAPPED - struct shmid_ds shm_data; - if ( shmctl(shm_id, IPC_STAT, &shm_data) < 0 ) { - Fatal("Can't shmctl: %s", strerror(errno)); - } - if ( shm_data.shm_nattch <= 1 ) { - if ( shmctl(shm_id, IPC_RMID, 0) < 0 ) { - Fatal("Can't shmctl: %s", strerror(errno)); - } - } -#endif // ZM_MEM_MAPPED - } // end if mem_ptr -} - -void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) { - for ( int i = 0; i < n_zones; i++ ) - delete zones[i]; - delete[] zones; - n_zones = p_n_zones; - zones = p_zones; -} - -void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) { - if ( privacy_bitmask ) { +void Monitor::AddPrivacyBitmask() { + if (privacy_bitmask) { delete[] privacy_bitmask; privacy_bitmask = nullptr; } Image *privacy_image = nullptr; - for ( int i = 0; i < n_zones; i++ ) { - if ( p_zones[i]->IsPrivacy() ) { - if ( !privacy_image ) { - privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE); + for (const Zone &zone : zones) { + //for (int i=0; i < zones.size(); i++) { + if (zone.IsPrivacy()) { + if (!privacy_image) { + privacy_image = new Image(width, height, 1, ZM_SUBPIX_ORDER_NONE); privacy_image->Clear(); } - privacy_image->Fill( 0xff, p_zones[i]->GetPolygon() ); - privacy_image->Outline( 0xff, p_zones[i]->GetPolygon() ); + privacy_image->Fill(0xff, zone.GetPolygon()); + privacy_image->Outline(0xff, zone.GetPolygon()); } } // end foreach zone - if ( privacy_image ) + if (privacy_image) privacy_bitmask = privacy_image->Buffer(); } -Monitor::State Monitor::GetState() const { - return (State)shared_data->state; -} - -int Monitor::GetImage( int index, int scale ) { +int Monitor::GetImage(int32_t index, int scale) { if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) { Image *image; // If we are going to be modifying the snapshot before writing, then we need to copy it if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; - - alarm_image.Assign(*snap_image); - - - //write_image.Assign( *snap_image ); + ZMPacket *snap = &image_buffer[index]; + alarm_image.Assign(*snap->image); if ( scale != ZM_SCALE_BASE ) { alarm_image.Scale(scale); @@ -828,7 +1193,7 @@ int Monitor::GetImage( int index, int scale ) { } static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Monitor%d.jpg", id); + snprintf(filename, sizeof(filename), "Monitor%u.jpg", id); image->WriteJpeg(filename); } else { Error("Unable to generate image, no images in buffer"); @@ -836,74 +1201,72 @@ int Monitor::GetImage( int index, int scale ) { return 0; } -struct timeval Monitor::GetTimestamp( int index ) const { - if ( index < 0 || index > image_buffer_count ) { +ZMPacket *Monitor::getSnapshot(int index) const { + + if ( (index < 0) || (index > image_buffer_count) ) { index = shared_data->last_write_index; } + return &image_buffer[index]; - if ( index != image_buffer_count ) { - Snapshot *snap = &image_buffer[index]; + return nullptr; +} - return *(snap->timestamp); - } else { - static struct timeval null_tv = { 0, 0 }; +struct timeval Monitor::GetTimestamp(int index) const { + ZMPacket *packet = getSnapshot(index); + if ( packet ) + return *packet->timestamp; - return null_tv; - } + static struct timeval null_tv = { 0, 0 }; + return null_tv; } unsigned int Monitor::GetLastReadIndex() const { - return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 ); + return ( shared_data->last_read_index != image_buffer_count ? shared_data->last_read_index : -1 ); } unsigned int Monitor::GetLastWriteIndex() const { - return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 ); + return ( shared_data->last_write_index != image_buffer_count ? shared_data->last_write_index : -1 ); } uint64_t Monitor::GetLastEventId() const { -#if 0 - Debug(2, "mem_ptr(%x), State(%d) last_read_index(%d) last_read_time(%d) last_event(%" PRIu64 ")", - mem_ptr, - shared_data->state, - shared_data->last_read_index, - shared_data->last_read_time, - shared_data->last_event - ); -#endif - return shared_data->last_event; + return shared_data->last_event_id; } // This function is crap. double Monitor::GetFPS() const { + return get_capture_fps(); // last_write_index is the last capture index. It starts as == image_buffer_count so that the first asignment % image_buffer_count = 0; - int index1 = shared_data->last_write_index; - if ( index1 == image_buffer_count ) { + int32_t index1 = shared_data->last_write_index; + if ( index1 >= image_buffer_count ) { // last_write_index only has this value on startup before capturing anything. return 0.0; } - Snapshot *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) { + Debug(2, "index1(%d)", index1); + ZMPacket *snap1 = &image_buffer[index1]; + if ( !snap1->timestamp->tv_sec ) { // This should be impossible Warning("Impossible situation. No timestamp on captured image index was %d, image-buffer_count was (%d)", index1, image_buffer_count); return 0.0; } struct timeval time1 = *snap1->timestamp; - int image_count = image_buffer_count; - int index2 = (index1+1)%image_buffer_count; - Snapshot *snap2 = &image_buffer[index2]; + int32_t fps_image_count = image_buffer_count; + + int32_t index2 = (index1+1)%image_buffer_count; + Debug(2, "index2(%d)", index2); + ZMPacket *snap2 = &image_buffer[index2]; // the timestamp pointers are initialized on connection, so that's redundant // tv_sec is probably only zero during the first loop of capturing, so this basically just counts the unused images. // The problem is that there is no locking, and we set the timestamp before we set last_write_index, // so there is a small window where the next image can have a timestamp in the future - while ( !snap2->timestamp || !snap2->timestamp->tv_sec || tvDiffSec(*snap2->timestamp, *snap1->timestamp) < 0 ) { + while ( !snap2->timestamp->tv_sec || tvDiffSec(*snap2->timestamp, *snap1->timestamp) < 0 ) { if ( index1 == index2 ) { // All images are uncaptured return 0.0; } index2 = (index2+1)%image_buffer_count; - snap2 = &image_buffer[index2]; - image_count--; + snap2 = &image_buffer[ index2 ]; + fps_image_count--; } struct timeval time2 = *snap2->timestamp; @@ -913,7 +1276,7 @@ double Monitor::GetFPS() const { time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); return 0.0; } - double curr_fps = image_count/time_diff; + double curr_fps = fps_image_count/time_diff; if ( curr_fps < 0.0 ) { Error("Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", @@ -926,21 +1289,27 @@ double Monitor::GetFPS() const { return curr_fps; } +/* I think this returns the # of micro seconds that we should sleep in order to maintain the desired analysis rate */ useconds_t Monitor::GetAnalysisRate() { - double capturing_fps = GetFPS(); - if ( !analysis_fps ) { + double capture_fps = get_capture_fps(); + if ( !analysis_fps_limit ) { return 0; - } else if ( analysis_fps > capturing_fps ) { - Warning("Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps); + } else if ( analysis_fps_limit > capture_fps ) { + if ( last_fps_time != last_analysis_fps_time ) { + // At startup they are equal, should never be equal again + Warning("Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps_limit, capture_fps); + } return 0; - } else { - return ( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) ); + } else if ( capture_fps ) { + return( ( 1000000 / analysis_fps_limit ) - ( 1000000 / capture_fps ) ); } + return 0; } void Monitor::UpdateAdaptiveSkip() { if ( config.opt_adaptive_skip ) { - double capturing_fps = GetFPS(); + double capturing_fps = get_capture_fps(); + double analysis_fps = get_analysis_fps(); if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) { Info("Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps); adaptive_skip = false; @@ -954,18 +1323,18 @@ void Monitor::UpdateAdaptiveSkip() { } void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) { - trigger_data->trigger_state = TRIGGER_ON; + trigger_data->trigger_state = TriggerState::TRIGGER_ON; trigger_data->trigger_score = force_score; strncpy(trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause)-1); strncpy(trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text)-1); } void Monitor::ForceAlarmOff() { - trigger_data->trigger_state = TRIGGER_OFF; + trigger_data->trigger_state = TriggerState::TRIGGER_OFF; } void Monitor::CancelForced() { - trigger_data->trigger_state = TRIGGER_CANCEL; + trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL; } void Monitor::actionReload() { @@ -975,25 +1344,17 @@ void Monitor::actionReload() { void Monitor::actionEnable() { shared_data->action |= RELOAD; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %d", id); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + char sql[ZM_SQL_SML_BUFSIZ]; + snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %u", id); + zmDbDo(sql); } void Monitor::actionDisable() { shared_data->action |= RELOAD; - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %d", id); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + char sql[ZM_SQL_SML_BUFSIZ]; + snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %u", id); + zmDbDo(sql); } void Monitor::actionSuspend() { @@ -1143,7 +1504,7 @@ void Monitor::DumpZoneImage(const char *zone_string) { if ( ( (!staticConfig.SERVER_ID) || ( staticConfig.SERVER_ID == server_id ) ) && mem_ptr ) { Debug(3, "Trying to load from local zmc"); int index = shared_data->last_write_index; - Snapshot *snap = &image_buffer[index]; + ZMPacket *snap = getSnapshot(index); zone_image = new Image(*snap->image); } else { Debug(3, "Trying to load from event"); @@ -1169,28 +1530,28 @@ void Monitor::DumpZoneImage(const char *zone_string) { zone_image->Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); } - for ( int i = 0; i < n_zones; i++ ) { - if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id ) + for (const Zone &zone : zones) { + if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zone.Id() == exclude_id ) continue; Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) { + if ( exclude_id && !extra_zone.getNumCoords() && zone.Id() == exclude_id ) { colour = extra_colour; } else { - if ( zones[i]->IsActive() ) { - colour = RGB_RED; - } else if ( zones[i]->IsInclusive() ) { - colour = RGB_ORANGE; - } else if ( zones[i]->IsExclusive() ) { - colour = RGB_PURPLE; - } else if ( zones[i]->IsPreclusive() ) { - colour = RGB_BLUE; + if ( zone.IsActive() ) { + colour = kRGBRed; + } else if ( zone.IsInclusive() ) { + colour = kRGBOrange; + } else if ( zone.IsExclusive() ) { + colour = kRGBPurple; + } else if ( zone.IsPreclusive() ) { + colour = kRGBBlue; } else { - colour = RGB_WHITE; + colour = kRGBWhite; } } - zone_image->Fill(colour, 2, zones[i]->GetPolygon()); - zone_image->Outline(colour, zones[i]->GetPolygon()); + zone_image->Fill(colour, 2, zone.GetPolygon()); + zone_image->Outline(colour, zone.GetPolygon()); } if ( extra_zone.getNumCoords() ) { @@ -1199,7 +1560,7 @@ void Monitor::DumpZoneImage(const char *zone_string) { } static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Zones%d.jpg", id); + snprintf(filename, sizeof(filename), "Zones%u.jpg", id); zone_image->WriteJpeg(filename); delete zone_image; } // end void Monitor::DumpZoneImage(const char *zone_string) @@ -1208,149 +1569,69 @@ void Monitor::DumpImage(Image *dump_image) const { if ( image_count && !(image_count%10) ) { static char filename[PATH_MAX]; static char new_filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Monitor%d.jpg", id); - snprintf(new_filename, sizeof(new_filename), "Monitor%d-new.jpg", id); + snprintf(filename, sizeof(filename), "Monitor%u.jpg", id); + snprintf(new_filename, sizeof(new_filename), "Monitor%u-new.jpg", id); if ( dump_image->WriteJpeg(new_filename) ) rename(new_filename, filename); } } // end void Monitor::DumpImage(Image *dump_image) bool Monitor::CheckSignal(const Image *image) { - static bool static_undef = true; - /* RGB24 colors */ - static uint8_t red_val; - static uint8_t green_val; - static uint8_t blue_val; - static uint8_t grayscale_val; /* 8bit grayscale color */ - static Rgb colour_val; /* RGB32 color */ - static int usedsubpixorder; + if (signal_check_points <= 0) + return true; - if ( signal_check_points > 0 ) { - if ( static_undef ) { - static_undef = false; - usedsubpixorder = camera->SubpixelOrder(); - colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ - colour_val = rgb_convert(colour_val, usedsubpixorder); - red_val = RED_VAL_BGRA(signal_check_colour); - green_val = GREEN_VAL_BGRA(signal_check_colour); - blue_val = BLUE_VAL_BGRA(signal_check_colour); - grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */ + const uint8_t *buffer = image->Buffer(); + int pixels = image->Pixels(); + int width = image->Width(); + int colours = image->Colours(); + + int index = 0; + for ( int i = 0; i < signal_check_points; i++ ) { + while ( true ) { + // Why the casting to long long? also note that on a 64bit cpu, long long is 128bits + index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); + if ( !config.timestamp_on_capture || !label_format[0] ) + break; + // Avoid sampling the rows with timestamp in + if ( index < (label_coord.Y()*width) || index >= (label_coord.Y()+Image::LINE_HEIGHT)*width ) + break; } - const uint8_t *buffer = image->Buffer(); - int pixels = image->Pixels(); - int width = image->Width(); - int colours = image->Colours(); + if ( colours == ZM_COLOUR_GRAY8 ) { + if ( *(buffer+index) != grayscale_val ) + return true; - int index = 0; - for ( int i = 0; i < signal_check_points; i++ ) { - while( true ) { - // Why the casting to long long? also note that on a 64bit cpu, long long is 128bits - index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); - if ( !config.timestamp_on_capture || !label_format[0] ) - break; - // Avoid sampling the rows with timestamp in - if ( index < (label_coord.Y()*width) || index >= (label_coord.Y()+Image::LINE_HEIGHT)*width ) - break; - } + } else if ( colours == ZM_COLOUR_RGB24 ) { + const uint8_t *ptr = buffer+(index*colours); - if ( colours == ZM_COLOUR_GRAY8 ) { - if ( *(buffer+index) != grayscale_val ) + if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR ) { + if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) + return true; + } else { + /* Assume RGB */ + if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) return true; - - } else if ( colours == ZM_COLOUR_RGB24 ) { - const uint8_t *ptr = buffer+(index*colours); - - if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR ) { - if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) - return true; - } else { - /* Assume RGB */ - if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) - return true; - } - - } else if ( colours == ZM_COLOUR_RGB32 ) { - if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR ) { - if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) - return true; - } else { - /* Assume RGBA or BGRA */ - if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) - return true; - } } - } // end for < signal_check_points - Debug(1, "SignalCheck: %d points, colour_val(%d)", signal_check_points, colour_val); - return false; - } // end if signal_check_points - return true; + + } else if ( colours == ZM_COLOUR_RGB32 ) { + if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR ) { + if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) + return true; + } else { + /* Assume RGBA or BGRA */ + if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) + return true; + } + } + } // end for < signal_check_points + Debug(1, "SignalCheck: %d points, colour_val(%d)", signal_check_points, colour_val); + return false; } // end bool Monitor::CheckSignal(const Image *image) -bool Monitor::Analyse() { - if ( shared_data->last_read_index == shared_data->last_write_index ) { - // I wonder how often this happens. Maybe if this happens we should sleep or something? - return false; - } - +void Monitor::CheckAction() { struct timeval now; gettimeofday(&now, nullptr); - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { - if ( now.tv_sec != last_fps_time ) { - double new_fps = double(fps_report_interval)/(now.tv_sec - last_fps_time); - Info("%s: %d - Analysing at %.2f fps", name, image_count, new_fps); - if ( fps != new_fps ) { - fps = new_fps; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "INSERT INTO Monitor_Status (MonitorId,AnalysisFPS) VALUES (%d, %.2lf) ON DUPLICATE KEY UPDATE AnalysisFPS = %.2lf", id, fps, fps); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); - } // end if fps != new_fps - - last_fps_time = now.tv_sec; - } - } - - int index; - if ( adaptive_skip ) { - // I think the idea behind adaptive skip is if we are falling behind, then skip a bunch, but not all - int read_margin = shared_data->last_read_index - shared_data->last_write_index; - if ( read_margin < 0 ) read_margin += image_buffer_count; - - int step = 1; - // Isn't read_margin always > 0 here? - if ( read_margin > 0 ) { - // TODO explain this so... 90% of image buffer / 50% of read margin? - step = (9*image_buffer_count)/(5*read_margin); - } - - int pending_frames = shared_data->last_write_index - shared_data->last_read_index; - if ( pending_frames < 0 ) pending_frames += image_buffer_count; - - Debug(4, - "ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d", - shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step - ); - if ( step <= pending_frames ) { - index = (shared_data->last_read_index+step)%image_buffer_count; - } else { - if ( pending_frames ) { - Warning("Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size"); - } - index = shared_data->last_write_index%image_buffer_count; - } - } else { - index = shared_data->last_write_index%image_buffer_count; - } - - Snapshot *snap = &image_buffer[index]; - struct timeval *timestamp = snap->timestamp; - Image *snap_image = snap->image; - if ( shared_data->action ) { // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? if ( shared_data->action & RELOAD ) { @@ -1370,13 +1651,11 @@ bool Monitor::Analyse() { auto_resume_time = now.tv_sec + config.max_suspend_time; } shared_data->action &= ~SUSPEND; - } - if ( shared_data->action & RESUME ) { + } else if ( shared_data->action & RESUME ) { if ( Enabled() && !Active() ) { Info("Received resume indication at count %d", image_count); shared_data->active = true; - ref_image = *snap_image; - ready_count = image_count+(warmup_count/2); + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread shared_data->alarm_x = shared_data->alarm_y = -1; } shared_data->action &= ~RESUME; @@ -1386,511 +1665,696 @@ bool Monitor::Analyse() { if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { Info("Auto resuming at count %d", image_count); shared_data->active = true; - ref_image = *snap_image; - ready_count = image_count+(warmup_count/2); + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread auto_resume_time = 0; } +} - if ( Enabled() ) { - bool signal = shared_data->signal; - bool signal_change = (signal != last_signal); +void Monitor::UpdateCaptureFPS() { + if ( fps_report_interval and + ( + !(image_count%fps_report_interval) + or + ( (image_count < fps_report_interval) and !(image_count%10) ) + ) + ) { + struct timeval now; + gettimeofday(&now, nullptr); + double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); + double elapsed = now_double - last_fps_time; - Debug(3, "Motion detection is enabled signal(%d) signal_change(%d)", signal, signal_change); + // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. + // Also only do the update at most 1/sec + if ( elapsed > 1.0 ) { + // # of images per interval / the amount of time it took + double new_capture_fps = double((image_count - last_capture_image_count)/elapsed); + unsigned int new_camera_bytes = camera->Bytes(); + unsigned int new_capture_bandwidth = (new_camera_bytes-last_camera_bytes)/elapsed; + last_camera_bytes = new_camera_bytes; - if ( trigger_data->trigger_state != TRIGGER_OFF ) { - unsigned int score = 0; - if ( Ready() ) { - std::string cause; - Event::StringSetMap noteSetMap; + Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps", "Capturing", image_count, + last_capture_image_count, image_count - last_capture_image_count, + now_double, last_analysis_fps_time, + elapsed, new_capture_fps + ); + Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", + name.c_str(), image_count, new_capture_fps, new_capture_bandwidth); + shared_data->capture_fps = new_capture_fps; + last_fps_time = now_double; + last_capture_image_count = image_count; - if ( trigger_data->trigger_state == TRIGGER_ON ) { - score += trigger_data->trigger_score; - Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); - if ( !event ) { - cause += trigger_data->trigger_cause; - } - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; + std::string sql = stringtf( + "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u", + new_capture_fps, new_capture_bandwidth, id); + dbQueue.push(std::move(sql)); + } // now != last_fps_time + } // end if report fps +} // void Monitor::UpdateCaptureFPS() + +void Monitor::UpdateAnalysisFPS() { + Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d", + analysis_image_count, motion_frame_count, fps_report_interval, + ((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) ); + + if ( + ( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) ) + or + // In startup do faster updates + ( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) ) + ) { + //if ( analysis_image_count && fps_report_interval && !(analysis_image_count%fps_report_interval) ) { + struct timeval now; + gettimeofday(&now, nullptr); + double now_double = (double)now.tv_sec + (0.000001f * now.tv_usec); + double elapsed = now_double - last_analysis_fps_time; + Debug(4, "%s: %d - now:%" PRIi64 ".%" PRIi64 " = %lf, last %lf, diff %lf", + name.c_str(), + analysis_image_count, + static_cast(now.tv_sec), + static_cast(now.tv_usec), + now_double, + last_analysis_fps_time, + elapsed); + + if ( elapsed > 1.0 ) { + double new_analysis_fps = double(motion_frame_count - last_motion_frame_count) / elapsed; + Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf", + name.c_str(), analysis_image_count, new_analysis_fps, + motion_frame_count, last_motion_frame_count, (motion_frame_count - last_motion_frame_count), + now_double, last_analysis_fps_time, elapsed); + + if ( new_analysis_fps != shared_data->analysis_fps ) { + shared_data->analysis_fps = new_analysis_fps; + + std::string sql = stringtf( + "UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u", + new_analysis_fps, id); + dbQueue.push(std::move(sql)); + last_analysis_fps_time = now_double; + last_motion_frame_count = motion_frame_count; + } else { + Debug(4, "No change in fps"); + } // end if change in fps + } // end if at least 1 second has passed since last update + + } // end if time to do an update +} // end void Monitor::UpdateAnalysisFPS + +// Would be nice if this JUST did analysis +// This idea is that we should be analysing as close to the capture frame as possible. +// This function should process as much as possible before returning +// +// If there is an event, the we should do our best to empty the queue. +// If there isn't then we keep pre-event + alarm frames. = pre_event_count +bool Monitor::Analyse() { + // if have event, send frames until we find a video packet, at which point do analysis. Adaptive skip should only affect which frames we do analysis on. + + // get_analysis_packet will lock the packet and may wait if analysis_it is at the end + ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + ZMPacket *snap = packet_lock->packet_; + + // Is it possible for snap->score to be ! -1 ? Not if everything is working correctly + if (snap->score != -1) { + delete packet_lock; + packetqueue.increment_it(analysis_it); + Error("skipping because score was %d", snap->score); + return false; + } + + packetqueue_iterator snap_it = *analysis_it; + packetqueue.increment_it(analysis_it); + + // signal is set by capture + bool signal = shared_data->signal; + bool signal_change = (signal != last_signal); + + Debug(3, "Motion detection is enabled signal(%d) signal_change(%d) trigger state(%s) image index %d", + signal, signal_change, TriggerState_Strings[trigger_data->trigger_state].c_str(), snap->image_index); + + // Need to guard around event creation/deletion from Reload() + std::lock_guard lck(event_mutex); + Debug(3, "Have event lock"); + + // if we have been told to be OFF, then we are off and don't do any processing. + if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { + Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); + int score = 0; + // Ready means that we have captured the warmup # of frames + if (!Ready()) { + Debug(3, "Not ready?"); + delete packet_lock; + return false; + } + + std::string cause; + Event::StringSetMap noteSetMap; + + // Specifically told to be on. Setting the score here will trigger the alarm. + if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { + score += trigger_data->trigger_score; + Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); + if (!event) { + cause += trigger_data->trigger_cause; + } + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + } // end if trigger_on + + // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. + if (signal_change) { + Debug(2, "Signal change, new signal is %d", signal); + const char *signalText = "Unknown"; + if (!signal) { + signalText = "Lost"; + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); + last_section_mod = 0; } + } else { + signalText = "Reacquired"; + score += 100; + } + if (!event) { + if (cause.length()) cause += ", "; + cause += SIGNAL_CAUSE; + } + Event::StringSet noteSet; + noteSet.insert(signalText); + noteSetMap[SIGNAL_CAUSE] = noteSet; + shared_data->state = state = IDLE; + shared_data->active = signal; + if ((function == MODECT or function == MOCORD) and snap->image) + ref_image.Assign(*(snap->image)); + }// else - if ( signal_change ) { - const char *signalText; - if ( !signal ) { - signalText = "Lost"; - } else { - signalText = "Reacquired"; - score += 100; - } - Warning("%s: %s", SIGNAL_CAUSE, signalText); - if ( event && !signal ) { - Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name, image_count, event->Id()); - closeEvent(); - } - if ( !event ) { - if ( cause.length() ) - cause += ", "; - cause += SIGNAL_CAUSE; - } + if (signal) { + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Check to see if linked monitors are triggering. + if (n_linked_monitors > 0) { + Debug(1, "Checking linked monitors"); + // FIXME improve logic here + bool first_link = true; Event::StringSet noteSet; - noteSet.insert(signalText); - noteSetMap[SIGNAL_CAUSE] = noteSet; - shared_data->state = state = IDLE; - shared_data->active = signal; - ref_image = *snap_image; - - } else if ( signal ) { - if ( Active() && (function == MODECT || function == MOCORD) ) { - // All is good, so add motion detection score. - Event::StringSet zoneSet; - if ( (!motion_frame_skip) || !(image_count % (motion_frame_skip+1)) ) { - // Get new score. - int new_motion_score = DetectMotion(*snap_image, zoneSet); - - Debug(3, - "After motion detection, last_motion_score(%d), new motion score(%d)", - last_motion_score, new_motion_score - ); - last_motion_score = new_motion_score; - } - if ( last_motion_score ) { - score += last_motion_score; - if ( !event ) { - if ( cause.length() ) - cause += ", "; - cause += MOTION_CAUSE; - } - noteSetMap[MOTION_CAUSE] = zoneSet; - } // end if motion_score - //shared_data->active = signal; // unneccessary active gets set on signal change - } // end if active and doing motion detection - - // Check to see if linked monitors are triggering. - if ( n_linked_monitors > 0 ) { - // FIXME improve logic here - bool first_link = true; - Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) { - // TODO: Shouldn't we try to connect? - if ( linked_monitors[i]->isConnected() ) { - if ( linked_monitors[i]->hasAlarmed() ) { - if ( !event ) { - if ( first_link ) { - if ( cause.length() ) - cause += ", "; - cause += LINKED_CAUSE; - first_link = false; - } + for ( int i = 0; i < n_linked_monitors; i++ ) { + // TODO: Shouldn't we try to connect? + if ( linked_monitors[i]->isConnected() ) { + Debug(1, "Linked monitor %d %s is connected", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if ( linked_monitors[i]->hasAlarmed() ) { + Debug(1, "Linked monitor %d %s is alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); + if ( !event ) { + if ( first_link ) { + if ( cause.length() ) + cause += ", "; + cause += LINKED_CAUSE; + first_link = false; } - noteSet.insert(linked_monitors[i]->Name()); - score += 50; } + noteSet.insert(linked_monitors[i]->Name()); + score += linked_monitors[i]->lastFrameScore(); // 50; } else { - Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); - linked_monitors[i]->connect(); + Debug(1, "Linked monitor %d %s is not alarmed", + linked_monitors[i]->Id(), linked_monitors[i]->Name()); } - } // end foreach linked_monit - if ( noteSet.size() > 0 ) - noteSetMap[LINKED_CAUSE] = noteSet; - } // end if linked_monitors + } else { + Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id()); + linked_monitors[i]->connect(); + } + } // end foreach linked_monitor + if ( noteSet.size() > 0 ) + noteSetMap[LINKED_CAUSE] = noteSet; + } // end if linked_monitors - //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? - if ( function == RECORD || function == MOCORD ) { - if ( event ) { - Debug(3, "Have signal and recording with open event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec); + struct timeval *timestamp = snap->timestamp; - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ( (function == MOCORD && (event_close_mode != CLOSE_TIME)) || ! ( timestamp->tv_sec % section_length ) ) - ) { - Info("%s: %03d - Closing event %" PRIu64 ", section end forced %d - %d = %d >= %d", - name, image_count, event->Id(), - timestamp->tv_sec, video_store_data->recording.tv_sec, - timestamp->tv_sec - video_store_data->recording.tv_sec, - section_length - ); - closeEvent(); - } // end if section_length - } // end if event + /* try to stay behind the decoder. */ + if (decoding_enabled) { + while (!snap->image and !snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + // Need to wait for the decoder thread. + Debug(1, "Waiting for decode"); + packet_lock->wait(); + if (!snap->image and snap->decoded) { + Debug(1, "No image but was decoded, giving up"); + delete packet_lock; + return false; + } + } // end while ! decoded + if (zm_terminate) { + delete packet_lock; + return false; + } + } // end if decoding enabled - if ( !event ) { - // Create event - event = new Event(this, *timestamp, "Continuous", noteSetMap, videoRecording); - shared_data->last_event = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; - Info("%s: %03d - Opening new event %" PRIu64 ", section start", name, image_count, event->Id()); + int motion_score = last_motion_score; - /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) { - shared_data->state = state = TAPE; + if (analysis_fps_limit) { + double capture_fps = get_capture_fps(); + motion_frame_skip = capture_fps / analysis_fps_limit; + Debug(1, "Recalculating motion_frame_skip (%d) = capture_fps(%f) / analysis_fps(%f)", + motion_frame_skip, capture_fps, analysis_fps_limit); + } + + if (!(analysis_image_count % (motion_frame_skip+1))) { + + if (snap->image) { + // decoder may not have been able to provide an image + if (!ref_image.Buffer()) { + Debug(1, "Assigning instead of Dectecting"); + ref_image.Assign(*(snap->image)); + } else { + Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); + // Get new score. + motion_score = DetectMotion(*(snap->image), zoneSet); + + snap->zone_stats.reserve(zones.size()); + for (const Zone &zone : zones) { + const ZoneStats &stats = zone.GetStats(); + stats.DumpToLog("After detect motion"); + snap->zone_stats.push_back(stats); + } + + Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", + score, last_motion_score, motion_score); + motion_frame_count += 1; + // Why are we updating the last_motion_score too? + last_motion_score = motion_score; } - } // end if ! event - } // end if function == RECORD || function == MOCORD) - } // end if !signal_change && signal + } else { + Debug(1, "no image so skipping motion detection"); + } // end if has image + } else { + Debug(1, "Skipped motion detection last motion score was %d", motion_score); + } + if (motion_score) { + score += motion_score; + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE; + noteSetMap[MOTION_CAUSE] = zoneSet; + } // end if motion_score + } else { + Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", + Active(), enabled, shared_data->active, + (function == MODECT or function == MOCORD) + ); + } // end if active and doing motion detection - if ( score ) { - if ( (state == IDLE) || (state == TAPE) || (state == PREALARM) ) { + if (function == RECORD or function == MOCORD) { + // If doing record, check to see if we need to close the event or not. + if (event) { + Debug(2, "Have event %" PRIu64 " in mocord", event->Id()); + + if (section_length && + ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) + && ( + ( (function == MOCORD) && (event_close_mode != CLOSE_TIME) ) + || + ( (function == RECORD) && (event_close_mode == CLOSE_TIME) ) + || ! ( timestamp->tv_sec % section_length ) + ) + ) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %d", + name.c_str(), + image_count, + event->Id(), + static_cast(timestamp->tv_sec), + static_cast(video_store_data->recording.tv_sec), + static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), + section_length); + closeEvent(); + } // end if section_length + } // end if event + + if (!event) { + Debug(2, "Creating continuous event"); + if (!snap->keyframe and (videowriter == PASSTHROUGH)) { + // Must start on a keyframe so rewind. Only for passthrough though I guess. + // FIXME this iterator is not protected from invalidation + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + snap_it, 0 /* pre_event_count */ + ); + + // This gets a lock on the starting packet + + ZMLockedPacket *starting_packet_lock = nullptr; + ZMPacket *starting_packet = nullptr; + if ( *start_it != snap_it ) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) return false; + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } + + event = new Event(this, *(starting_packet->timestamp), "Continuous", noteSetMap); + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while ( starting_packet and ((*start_it) != snap_it) ) { + event->AddPacket(starting_packet); + // Have added the packet, don't want to unlock it until we have locked the next + + packetqueue.increment_it(start_it); + if ( (*start_it) == snap_it ) { + if (starting_packet_lock) delete starting_packet_lock; + break; + } + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) return false; + starting_packet_lock = lp; + starting_packet = lp->packet_; + } + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + } else { + // Create event from current snap + event = new Event(this, *timestamp, "Continuous", noteSetMap); + } + shared_data->last_event_id = event->Id(); + + // lets construct alarm cause. It will contain cause + names of zones alarmed + std::string alarm_cause; + for (const Zone &zone : zones) { + if (zone.Alarmed()) { + if (!alarm_cause.empty()) alarm_cause += ","; + alarm_cause += std::string(zone.Label()); + } + } + alarm_cause = cause+" Continuous "+alarm_cause; + strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); + video_store_data->recording = event->StartTime(); + Info("%s: %03d - Opened new event %" PRIu64 ", section start", + name.c_str(), analysis_image_count, event->Id()); + /* To prevent cancelling out an existing alert\prealarm\alarm state */ + if (state == IDLE) { + shared_data->state = state = TAPE; + } + } // end if ! event + } // end if RECORDING + + if (score) { + + if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event - if ( event && event->Frames() + if (event && event->Frames() && (!event->AlarmFrames()) && (event_close_mode == CLOSE_ALARM) && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - && ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) + && ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) ) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", - name, image_count, event->Id()); + name.c_str(), image_count, event->Id()); closeEvent(); - } else if ( event ) { - // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames - Debug(3, "pre-alarm-count in event %d, event frames %d, alarm frames %d event length %d >=? %d min", - Event::PreAlarmCount(), event->Frames(), event->AlarmFrames(), - ( timestamp->tv_sec - video_store_data->recording.tv_sec ), min_section_length - ); - } - - if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) { - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause = ""; - for ( int i=0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - alarm_cause = alarm_cause + "," + std::string(zones[i]->Label()); + } else if (event) { + // This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames + Debug(3, + "pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %d min", + Event::PreAlarmCount(), + event->Frames(), + event->AlarmFrames(), + static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), + min_section_length); + } + if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { + // lets construct alarm cause. It will contain cause + names of zones alarmed + std::string alarm_cause = ""; + for (const Zone &zone : zones) { + if (zone.Alarmed()) { + alarm_cause = alarm_cause + "," + std::string(zone.Label()); } } - if ( !alarm_cause.empty() ) alarm_cause[0] = ' '; + if (!alarm_cause.empty()) alarm_cause[0] = ' '; alarm_cause = cause + alarm_cause; strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name, image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause); + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause); - if ( !event ) { - int pre_index; - int pre_event_images = pre_event_count; - - if ( analysis_fps && pre_event_count ) { - // If analysis fps is set, - // compute the index for pre event images in the dedicated buffer - pre_index = pre_event_buffer_count ? image_count % pre_event_buffer_count : 0; - Debug(3, "pre-index %d = image_count(%d) %% pre_event_buffer_count(%d)", - pre_index, image_count, pre_event_buffer_count); - - // Seek forward the next filled slot in to the buffer (oldest data) - // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { - pre_index = (pre_index + 1)%pre_event_buffer_count; - // Slot is empty, removing image from counter - pre_event_images--; - } - Debug(3, "pre-index %d, pre-event_images %d", - pre_index, pre_event_images); - - event = new Event(this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap); + if (!event) { + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + snap_it, + (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) + ); + ZMLockedPacket *starting_packet_lock = nullptr; + ZMPacket *starting_packet = nullptr; + if (*start_it != snap_it) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) return false; + starting_packet = starting_packet_lock->packet_; } else { - // If analysis fps is not set (analysis performed at capturing framerate), - // compute the index for pre event images in the capturing buffer - if ( alarm_frame_count > 1 ) - pre_index = ((index + image_buffer_count) - ((alarm_frame_count - 1) + pre_event_count))%image_buffer_count; - else - pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; + starting_packet = snap; + } - Debug(3, "Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d)", - pre_index, index, image_buffer_count, pre_event_count); - - // Seek forward the next filled slot in to the buffer (oldest data) - // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { - pre_index = (pre_index + 1)%image_buffer_count; - // Slot is empty, removing image from counter - pre_event_images--; - } - - event = new Event(this, *(image_buffer[pre_index].timestamp), cause, noteSetMap); - } // end if analysis_fps && pre_event_count - - shared_data->last_event = event->Id(); - //set up video store data + event = new Event(this, *(starting_packet->timestamp), cause, noteSetMap); + shared_data->last_event_id = event->Id(); snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); video_store_data->recording = event->StartTime(); + shared_data->state = state = ALARM; - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name, image_count, event->Id()); + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while (*start_it != snap_it) { + event->AddPacket(starting_packet); - if ( pre_event_images ) { - if ( analysis_fps ) { - for ( int i = 0; i < pre_event_images; i++ ) { - timestamps[i] = pre_event_buffer[pre_index].timestamp; - images[i] = pre_event_buffer[pre_index].image; - pre_index = (pre_index + 1)%pre_event_buffer_count; - } - } else { - for ( int i = 0; i < pre_event_images; i++ ) { - timestamps[i] = image_buffer[pre_index].timestamp; - images[i] = image_buffer[pre_index].image; - pre_index = (pre_index + 1)%image_buffer_count; - } + packetqueue.increment_it(start_it); + if ( (*start_it) == snap_it ) { + if (starting_packet_lock) delete starting_packet_lock; + break; } - event->AddFrames(pre_event_images, images, timestamps); - } // end if pre_event_images - - if ( ( alarm_frame_count > 1 ) && Event::PreAlarmCount() ) { - Debug(1, "alarm frame count so SavePreAlarmFrames"); - event->SavePreAlarmFrames(); + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) { + // Shutting down event will be closed by ~Monitor() + // Perhaps we shouldn't do this. + return false; + } + starting_packet_lock = lp; + starting_packet = lp->packet_; } - } // end if event - shared_data->state = state = ALARM; - } else if ( state != PREALARM ) { - Info("%s: %03d - Gone into prealarm state", name, image_count); + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } else { + shared_data->state = state = ALARM; + } // end if no event, so start it + if ( alarm_frame_count ) { + Debug(1, "alarm frame count so SavePreAlarmFrames"); + event->SavePreAlarmFrames(); + } + } else if (state != PREALARM) { + Info("%s: %03d - Gone into prealarm state", name.c_str(), analysis_image_count); shared_data->state = state = PREALARM; } - } else if ( state == ALERT ) { - Info("%s: %03d - Gone back into alarm state", name, image_count); + } else if (state == ALERT) { + alert_to_alarm_frame_count--; + Info("%s: %03d - Alarmed frame while in alert state. Consecutive alarmed frames left to return to alarm state: %03d", + name.c_str(), analysis_image_count, alert_to_alarm_frame_count); + if (alert_to_alarm_frame_count == 0) { + Info("%s: %03d - Gone back into alarm state", name.c_str(), analysis_image_count); + shared_data->state = state = ALARM; + } + } else if (state == TAPE) { + // Already recording, but IDLE so switch to ALARM shared_data->state = state = ALARM; + Debug(1, "Was in TAPE, going into ALARM"); + } else { + Debug(1, "Staying in %s", State_Strings[state].c_str()); + } - last_alarm_count = image_count; - } else { // not score - if ( state == ALARM ) { - Info("%s: %03d - Gone into alert state", name, image_count); + if (state == ALARM) { + last_alarm_count = analysis_image_count; + } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT + } else { // no score? + alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + if (state == ALARM) { + Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; - } else if ( state == ALERT ) { + } else if (state == ALERT) { if ( - ( image_count-last_alarm_count > post_event_count ) - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - ) { + ( analysis_image_count-last_alarm_count > post_event_count ) + && + ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) + ) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", - name, image_count, event->Id(), event->Frames(), event->AlarmFrames()); + name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) - if ( ( function != MOCORD && function != RECORD ) || event_close_mode == CLOSE_ALARM ) { + if ( (function != RECORD && function != MOCORD ) || event_close_mode == CLOSE_ALARM ) { shared_data->state = state = IDLE; Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", - name, image_count, event->Id(), (function==MOCORD)?", section truncated":""); + name.c_str(), analysis_image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); closeEvent(); } else { shared_data->state = state = TAPE; } } - } // end if ALARM or ALERT - - if ( state == PREALARM ) { - if ( function != MOCORD ) { - shared_data->state = state = IDLE; - } else { - shared_data->state = state = TAPE; - } - // Not in PREALARM state anymore, can clear PreAlarmCount - if ( Event::PreAlarmCount() ) - Event::EmptyPreAlarmFrames(); - } - } // end if score or not - - if ( state != IDLE ) { - if ( state == PREALARM || state == ALARM ) { - if ( savejpegs > 1 ) { - bool got_anal_image = false; - alarm_image.Assign(*snap_image); - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - alarm_image.Overlay(*(zones[i]->AlarmImage())); - got_anal_image = true; - } - if ( config.record_event_stats && (state == ALARM) ) - zones[i]->RecordStats(event); - } // end if zone is alarmed - } // end foreach zone - - if ( state == PREALARM ) { - Event::AddPreAlarmFrame(snap_image, *timestamp, score, (got_anal_image?&alarm_image:nullptr)); - } else { - event->AddFrame(snap_image, *timestamp, score, (got_anal_image?&alarm_image:nullptr)); - } - } else { - // Not doing alarm frame storage - if ( state == PREALARM ) { - Event::AddPreAlarmFrame(snap_image, *timestamp, score); - } else { - event->AddFrame(snap_image, *timestamp, score); - if ( config.record_event_stats ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) - zones[i]->RecordStats(event); - } - } // end if config.record_event_stats - } - } // end if savejpegs > 1 - - if ( event ) { - if ( noteSetMap.size() > 0 ) - event->updateNotes(noteSetMap); - - if ( section_length - && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) - && ! (image_count % fps_report_interval) - ) { - Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %d - %d = %d >= %d", - name, image_count, event->Id(), - timestamp->tv_sec, video_store_data->recording.tv_sec, - timestamp->tv_sec - video_store_data->recording.tv_sec, - section_length - ); - closeEvent(); - event = new Event(this, *timestamp, cause, noteSetMap); - shared_data->last_event = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - video_store_data->recording = event->StartTime(); - } - } // end if event - - } else if ( state == ALERT ) { - event->AddFrame(snap_image, *timestamp); - if ( noteSetMap.size() > 0 ) - event->updateNotes(noteSetMap); - } else if ( state == TAPE ) { - //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly - //if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { - // I don't think this is required, and causes problems, as the event file hasn't been setup yet. - //Warning("In state TAPE, - //video_store_data->recording = event->StartTime(); - //} - if ( (!frame_skip) || !(image_count%(frame_skip+1)) ) { - if ( config.bulk_frame_interval > 1 ) { - event->AddFrame(snap_image, *timestamp, (event->Frames()AddFrame(snap_image, *timestamp); - } - } + } else if (state == PREALARM) { + // Back to IDLE + shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); + } else { + Debug(1, + "State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%d)", + State_Strings[state].c_str(), + analysis_image_count, + last_alarm_count, + post_event_count, + static_cast(timestamp->tv_sec), + static_cast(video_store_data->recording.tv_sec), + min_section_length); } - } // end if ! IDLE - } - } else { - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name, image_count, event->Id()); - closeEvent(); - } - shared_data->state = state = IDLE; - trigger_data->trigger_state = TRIGGER_CANCEL; - } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) + if (Event::PreAlarmCount()) + Event::EmptyPreAlarmFrames(); + } // end if score or not - if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { - if ( state == ALARM ) { - ref_image.Blend( *snap_image, alarm_ref_blend_perc ); - } else { - ref_image.Blend( *snap_image, ref_blend_perc ); - } + snap->score = score; + + if (state == PREALARM) { + // Generate analysis images if necessary + if ((savejpegs > 1) and snap->image) { + for (const Zone &zone : zones) { + if (zone.Alarmed()) { + if (zone.AlarmImage()) { + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); + snap->analysis_image->Overlay(*(zone.AlarmImage())); + } + } // end if zone is alarmed + } // end foreach zone + } // end if savejpegs + + // incremement pre alarm image count + //have_pre_alarmed_frames ++; + Event::AddPreAlarmFrame(snap->image, *timestamp, score, nullptr); + } else if (state == ALARM) { + for (const Zone &zone : zones) { + if (zone.Alarmed()) { + if (zone.AlarmImage() and (savejpegs > 1) and snap->image) { + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); + snap->analysis_image->Overlay(*(zone.AlarmImage())); + } + } // end if zone is alarmed + } // end foreach zone + if (event) { + if (noteSetMap.size() > 0) + event->updateNotes(noteSetMap); + if ( section_length + && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) + ) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %d", + name.c_str(), analysis_image_count, event->Id(), + static_cast(timestamp->tv_sec), static_cast(video_store_data->recording.tv_sec), + static_cast(timestamp->tv_sec - video_store_data->recording.tv_sec), + section_length); + closeEvent(); + event = new Event(this, *timestamp, cause, noteSetMap); + shared_data->last_event_id = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); + } + } else { + Error("ALARM but no event"); + } + } else if ( state == ALERT ) { + // Alert means this frame has no motion, but we were alarmed and are still recording. + if ( noteSetMap.size() > 0 ) + event->updateNotes(noteSetMap); + } else if ( state == TAPE ) { + // bulk frame code moved to event. + } // end if state machine + + if ( (function == MODECT or function == MOCORD) and snap->image ) { + if (!ref_image.Buffer()) { + Debug(1, "Assigning"); + ref_image.Assign(*(snap->image)); + } else { + Debug(1, "Blending"); + ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + Debug(1, "Done Blending"); + } + } + last_signal = signal; + } // end if videostream + } // end if signal + shared_data->last_frame_score = score; + } else { + Debug(3, "trigger == off"); + if ( event ) { + Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); + closeEvent(); } - last_signal = signal; - } // end if Enabled() + shared_data->state = state = IDLE; + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - shared_data->last_read_index = index % image_buffer_count; - //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec; - shared_data->last_read_time = now.tv_sec; + if (event) event->AddPacket(snap); - if ( analysis_fps && pre_event_buffer_count ) { - // If analysis fps is set, add analysed image to dedicated pre event buffer - int pre_index = image_count%pre_event_buffer_count; - pre_event_buffer[pre_index].image->Assign(*snap->image); - memcpy(pre_event_buffer[pre_index].timestamp, snap->timestamp, sizeof(struct timeval)); + // In the case where people have pre-alarm frames, the web ui will generate the frame images + // from the mp4. So no one will notice anyways. + if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; } - image_count++; + packetqueue.unlock(packet_lock); + + if ( snap->image_index > 0 ) { + // Only do these if it's a video packet. + shared_data->last_read_index = snap->image_index; + analysis_image_count++; + if ( function == MODECT or function == MOCORD ) + UpdateAnalysisFPS(); + } + shared_data->last_read_time = time(nullptr); + packetqueue.clearPackets(snap); return true; -} // end Monitor::Analyze +} // end Monitor::Analyse void Monitor::Reload() { - Debug(1, "Reloading monitor %s", name); + Debug(1, "Reloading monitor %s", name.c_str()); - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", reloading", name, image_count, event->Id()); + // Access to the event needs to be protected. Either thread could call Reload. Either thread could close the event. + // Need a mutex on it I guess. FIXME + // Need to guard around event creation/deletion This will prevent event creation until new settings are loaded + std::lock_guard lck(event_mutex); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", reloading", name.c_str(), image_count, event->Id()); closeEvent(); } - static char sql[ZM_SQL_MED_BUFSIZ]; - // This seems to have fallen out of date. - snprintf(sql, sizeof(sql), - "SELECT `Function`+0, `Enabled`, `LinkedMonitors`, `EventPrefix`, `LabelFormat`, " - "`LabelX`, `LabelY`, `LabelSize`, `WarmupCount`, `PreEventCount`, `PostEventCount`, " - "`AlarmFrameCount`, `SectionLength`, `MinSectionLength`, `FrameSkip`, " - "`MotionFrameSkip`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`, " - "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, " - "`SignalCheckPoints`, `SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id); - - zmDbRow *row = zmDbFetchOne(sql); - if ( !row ) { + std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", id); + zmDbRow *row = zmDbFetchOne(sql.c_str()); + if (!row) { Error("Can't run query: %s", mysql_error(&dbconn)); - } else if ( MYSQL_ROW dbrow = row->mysql_row() ) { - int index = 0; - function = (Function)atoi(dbrow[index++]); - enabled = atoi(dbrow[index++]); - const char *p_linked_monitors = dbrow[index++]; + } else if (MYSQL_ROW dbrow = row->mysql_row()) { + Load(dbrow, true /*load zones */, purpose); - if ( dbrow[index] ) { - strncpy(event_prefix, dbrow[index++], sizeof(event_prefix)-1); - } else { - event_prefix[0] = 0; - index++; - } - if ( dbrow[index] ) { - strncpy(label_format, dbrow[index++], sizeof(label_format)-1); - } else { - label_format[0] = 0; - index++; - } - - label_coord = Coord( atoi(dbrow[index]), atoi(dbrow[index+1]) ); index += 2; - label_size = atoi(dbrow[index++]); - warmup_count = atoi(dbrow[index++]); - pre_event_count = atoi(dbrow[index++]); - post_event_count = atoi(dbrow[index++]); - alarm_frame_count = atoi(dbrow[index++]); - section_length = atoi(dbrow[index++]); - min_section_length = atoi(dbrow[index++]); - frame_skip = atoi(dbrow[index++]); - motion_frame_skip = atoi(dbrow[index++]); - analysis_fps = dbrow[index] ? strtod(dbrow[index], nullptr) : 0; index++; - analysis_update_delay = strtoul(dbrow[index++], nullptr, 0); - - capture_max_fps = dbrow[index] ? atof(dbrow[index]) : 0.0; index++; - capture_delay = ( capture_max_fps > 0.0 ) ? int(DT_PREC_3/capture_max_fps) : 0; - - alarm_capture_delay = (dbrow[index]&&atof(dbrow[index])>0.0)?int(DT_PREC_3/atof(dbrow[index])):0; index++; - fps_report_interval = atoi(dbrow[index++]); - ref_blend_perc = atoi(dbrow[index++]); - alarm_ref_blend_perc = atoi(dbrow[index++]); - track_motion = atoi(dbrow[index++]); - - signal_check_points = dbrow[index]?atoi(dbrow[index]):0; index++; - signal_check_colour = strtol(dbrow[index][0]=='#'?dbrow[index]+1:dbrow[index], 0, 16); index++; - - shared_data->state = state = IDLE; - shared_data->alarm_x = shared_data->alarm_y = -1; - if ( enabled ) - shared_data->active = true; - ready_count = image_count+warmup_count; - - ReloadLinkedMonitors(p_linked_monitors); delete row; - } // end if row + } // end if row - ReloadZones(); } // end void Monitor::Reload() void Monitor::ReloadZones() { - Debug(1, "Reloading zones for monitor %s", name); - for ( int i = 0; i < n_zones; i++ ) { - delete zones[i]; - } - delete[] zones; - zones = nullptr; - n_zones = Zone::Load(this, zones); + Debug(3, "Reloading zones for monitor %s have %zu", name.c_str(), zones.size()); + zones = Zone::Load(this); + Debug(1, "Reloading zones for monitor %s have %zu", name.c_str(), zones.size()); + this->AddPrivacyBitmask(); //DumpZoneImage(); } // end void Monitor::ReloadZones() void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { - Debug(1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors); + Debug(1, "Reloading linked monitors for monitor %s, '%s'", name.c_str(), p_linked_monitors); if ( n_linked_monitors ) { - for ( int i = 0; i < n_linked_monitors; i++ ) { + for ( int i=0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; @@ -1902,6 +2366,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { int n_link_ids = 0; unsigned int link_ids[256]; + // This nasty code picks out strings of digits from p_linked_monitors and tries to load them. char link_id_str[8]; char *dest_ptr = link_id_str; const char *src_ptr = p_linked_monitors; @@ -1945,639 +2410,205 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { for ( int i = 0; i < n_link_ids; i++ ) { Debug(1, "Checking linked monitor %d", link_ids[i]); - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), + std::string sql = stringtf( "SELECT `Id`, `Name` FROM `Monitors`" " WHERE `Id` = %d" " AND `Function` != 'None'" " AND `Function` != 'Monitor'" " AND `Enabled`=1", - link_ids[i] ); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't run query: %s", mysql_error(&dbconn)); + link_ids[i]); + + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) { continue; } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - db_mutex.unlock(); - Error("Can't use query result: %s", mysql_error(&dbconn)); - continue; - } - db_mutex.unlock(); int n_monitors = mysql_num_rows(result); if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row(result); - Debug(1, "Linking to monitor %d", link_ids[i]); + Debug(1, "Linking to monitor %d %s", atoi(dbrow[0]), dbrow[1]); linked_monitors[count++] = new MonitorLink(link_ids[i], dbrow[1]); } else { Warning("Can't link to monitor %d, invalid id, function or not enabled", link_ids[i]); } mysql_free_result(result); - } // end foreach link_id + } // end foreach link_id n_linked_monitors = count; - } // end if has link_ids - } // end if p_linked_monitors -} // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) - -int Monitor::LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose) { + } // end if has link_ids + } // end if p_linked_monitors +} // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) +std::vector> Monitor::LoadMonitors(std::string &where, Purpose purpose) { + std::string sql = load_monitor_sql + " WHERE " + where; Debug(1, "Loading Monitors with %s", sql.c_str()); MYSQL_RES *result = zmDbFetch(sql.c_str()); - if ( !result ) { + if (!result) { Error("Can't load local monitors: %s", mysql_error(&dbconn)); - return 0; + return {}; } int n_monitors = mysql_num_rows(result); Debug(1, "Got %d monitors", n_monitors); - delete[] monitors; - monitors = new Monitor *[n_monitors]; - for ( int i=0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { - monitors[i] = Monitor::Load(dbrow, 1, purpose); + + std::vector> monitors; + monitors.reserve(n_monitors); + + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { + monitors.emplace_back(std::make_shared()); + monitors.back()->Load(dbrow, true, purpose); } - if ( mysql_errno(&dbconn) ) { + + if (mysql_errno(&dbconn)) { Error("Can't fetch row: %s", mysql_error(&dbconn)); - return 0; + mysql_free_result(result); + return {}; } mysql_free_result(result); - return n_monitors; -} // end int Monitor::LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose) + return monitors; +} #if ZM_HAS_V4L -int Monitor::LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose) { +std::vector> Monitor::LoadLocalMonitors +(const char *device, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'Local'"; + std::string where = "`Function` != 'None' AND `Type` = 'Local'"; if ( device[0] ) - sql += " AND `Device`='" + std::string(device) + "'"; - if ( staticConfig.SERVER_ID ) - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose) + where += " AND `Device`='" + std::string(device) + "'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + return LoadMonitors(where, purpose); +} #endif // ZM_HAS_V4L -int Monitor::LoadRemoteMonitors(const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'Remote'"; - if ( staticConfig.SERVER_ID ) - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); +std::vector> Monitor::LoadRemoteMonitors +(const char *protocol, const char *host, const char *port, const char *path, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'Remote'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + if (protocol) + where += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path); + return LoadMonitors(where, purpose); +} - if ( protocol ) - sql += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path); - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadRemoteMonitors - -int Monitor::LoadFileMonitors(const char *file, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'File'"; - if ( file[0] ) - sql += " AND `Path`='" + std::string(file) + "'"; - if ( staticConfig.SERVER_ID ) { - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); - } - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadFileMonitors +std::vector> Monitor::LoadFileMonitors(const char *file, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'File'"; + if (file[0]) + where += " AND `Path`='" + std::string(file) + "'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + return LoadMonitors(where, purpose); +} #if HAVE_LIBAVFORMAT -int Monitor::LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose) { - std::string sql = load_monitor_sql + " WHERE `Function` != 'None' AND `Type` = 'Ffmpeg'"; - if ( file[0] ) - sql += " AND `Path` = '" + std::string(file) + "'"; - - if ( staticConfig.SERVER_ID ) { - sql += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); - } - return LoadMonitors(sql, monitors, purpose); -} // end int Monitor::LoadFfmpegMonitors +std::vector> Monitor::LoadFfmpegMonitors(const char *file, Purpose purpose) { + std::string where = "`Function` != 'None' AND `Type` = 'Ffmpeg'"; + if (file[0]) + where += " AND `Path` = '" + std::string(file) + "'"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + return LoadMonitors(where, purpose); +} #endif // HAVE_LIBAVFORMAT -/* For reference -std::string load_monitor_sql = -"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, " -"`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," -"`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings -"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " -"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " -"`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " -//" OutputCodec, Encoder, OutputContainer, " -"`RecordAudio`, " -"`Brightness`, `Contrast`, `Hue`, `Colour`, " -"`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`," -"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " -"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " -"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`"; -*/ - -Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { - int col = 0; - - int id = atoi(dbrow[col]); col++; - const char *name = dbrow[col]; col++; - int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; - int storage_id = atoi(dbrow[col]); col++; - std::string type = dbrow[col] ? dbrow[col] : ""; col++; - Function function = (Function)atoi(dbrow[col]); col++; - int enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; - const char *linked_monitors = dbrow[col];col++; - - double analysis_fps = dbrow[col] ? strtod(dbrow[col], nullptr) : 0; col++; - unsigned int analysis_update_delay = strtoul(dbrow[col++], nullptr, 0); - - double capture_max_fps = dbrow[col] ? atof(dbrow[col]) : 0.0; col++; - double capture_delay = ( capture_max_fps > 0.0 ) ? int(DT_PREC_3/capture_max_fps) : 0; - unsigned int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; - - const char *device = dbrow[col]; col++; - int channel = atoi(dbrow[col]); col++; - int format = atoi(dbrow[col]); col++; - bool v4l_multi_buffer = config.v4l_multi_buffer; - if ( dbrow[col] ) { - if (*dbrow[col] == '0' ) { - v4l_multi_buffer = false; - } else if ( *dbrow[col] == '1' ) { - v4l_multi_buffer = true; - } - } - col++; - - int v4l_captures_per_frame = 0; - if ( dbrow[col] ) { - v4l_captures_per_frame = atoi(dbrow[col]); - } else { - v4l_captures_per_frame = config.captures_per_frame; - } - Debug(1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame); - col++; - - std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; - std::string method = dbrow[col] ? dbrow[col] : ""; col++; - std::string options = dbrow[col] ? dbrow[col] : ""; col++; - std::string user = dbrow[col] ? dbrow[col] : ""; col++; - std::string pass = dbrow[col] ? dbrow[col] : ""; col++; - std::string host = dbrow[col] ? dbrow[col] : ""; col++; - std::string port = dbrow[col] ? dbrow[col] : ""; col++; - std::string path = dbrow[col] ? dbrow[col] : ""; col++; - int width = atoi(dbrow[col]); col++; - int height = atoi(dbrow[col]); col++; - int colours = atoi(dbrow[col]); col++; - int palette = atoi(dbrow[col]); col++; - Orientation orientation = (Orientation)atoi(dbrow[col]); col++; - int deinterlacing = atoi(dbrow[col]); col++; - std::string decoder_hwaccel_name = dbrow[col] ? dbrow[col] : ""; col++; - std::string decoder_hwaccel_device = dbrow[col] ? dbrow[col] : ""; col++; - - bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; - - int savejpegs = atoi(dbrow[col]); col++; - VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; - const char *encoderparams = dbrow[col] ? dbrow[col] : ""; col++; - bool record_audio = (*dbrow[col] != '0'); col++; - - int brightness = atoi(dbrow[col]); col++; - int contrast = atoi(dbrow[col]); col++; - int hue = atoi(dbrow[col]); col++; - int colour = atoi(dbrow[col]); col++; - - const char *event_prefix = dbrow[col]; col ++; - const char *label_format = dbrow[col] ? dbrow[col] : ""; col ++; - Coord label_coord = Coord( atoi(dbrow[col]), atoi(dbrow[col+1]) ); col += 2; - int label_size = atoi(dbrow[col]); col++; - - int image_buffer_count = atoi(dbrow[col]); col++; - int warmup_count = atoi(dbrow[col]); col++; - int pre_event_count = atoi(dbrow[col]); col++; - int post_event_count = atoi(dbrow[col]); col++; - int stream_replay_buffer = atoi(dbrow[col]); col++; - int alarm_frame_count = atoi(dbrow[col]); col++; - int section_length = atoi(dbrow[col]); col++; - int min_section_length = atoi(dbrow[col]); col++; - int frame_skip = atoi(dbrow[col]); col++; - int motion_frame_skip = atoi(dbrow[col]); col++; - int fps_report_interval = atoi(dbrow[col]); col++; - int ref_blend_perc = atoi(dbrow[col]); col++; - int alarm_ref_blend_perc = atoi(dbrow[col]); col++; - int track_motion = atoi(dbrow[col]); col++; - bool embed_exif = (*dbrow[col] != '0'); col++; - int signal_check_points = dbrow[col] ? atoi(dbrow[col]) : 0;col++; - int signal_check_color = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], nullptr, 16); col++; - - Camera *camera = nullptr; - if ( type == "Local" ) { - -#if ZM_HAS_V4L - int extras = (deinterlacing>>24)&0xff; - - camera = new LocalCamera( - id, - device, - channel, - format, - v4l_multi_buffer, - v4l_captures_per_frame, - method, - width, - height, - colours, - palette, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio, - extras - ); -#else - Fatal("ZoneMinder not built with Local Camera support"); -#endif - } else if ( type == "Remote" ) { - if ( protocol == "http" ) { - camera = new RemoteCameraHttp( - id, - method, - host, - port, - path, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } -#if HAVE_LIBAVFORMAT - else if ( protocol == "rtsp" ) { - camera = new RemoteCameraRtsp( - id, - method, - host, // Host - port, // Port - path, // Path - width, - height, - rtsp_describe, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } -#endif // HAVE_LIBAVFORMAT - else { - Fatal("Unexpected remote camera protocol '%s'", protocol.c_str()); - } - } else if ( type == "File" ) { - camera = new FileCamera( - id, - path.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } else if ( type == "Ffmpeg" ) { -#if HAVE_LIBAVFORMAT - camera = new FfmpegCamera( - id, - path, - method, - options, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio, - decoder_hwaccel_name, - decoder_hwaccel_device - ); -#endif // HAVE_LIBAVFORMAT - } else if ( type == "NVSocket" ) { - camera = new RemoteCameraNVSocket( - id, - host.c_str(), - port.c_str(), - path.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); - } else if ( type == "Libvlc" ) { -#if HAVE_LIBVLC - camera = new LibvlcCamera( - id, - path.c_str(), - method, - options, - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); -#else // HAVE_LIBVLC - Fatal("You must have vlc libraries installed to use vlc cameras for monitor %d", id); -#endif // HAVE_LIBVLC - } else if ( type == "cURL" ) { -#if HAVE_LIBCURL - camera = new cURLCamera( - id, - path.c_str(), - user.c_str(), - pass.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); -#else // HAVE_LIBCURL - Fatal("You must have libcurl installed to use ffmpeg cameras for monitor %d", id); -#endif // HAVE_LIBCURL - } else if ( type == "VNC" ) { -#if HAVE_LIBVNC - camera = new VncCamera( - id, - host.c_str(), - port.c_str(), - user.c_str(), - pass.c_str(), - width, - height, - colours, - brightness, - contrast, - hue, - colour, - purpose==CAPTURE, - record_audio - ); -#else // HAVE_LIBVNC - Fatal("You must have libvnc installed to use VNC cameras for monitor id %d", id); -#endif // HAVE_LIBVNC - } else { - Fatal("Bogus monitor type '%s' for monitor %d", type.c_str(), id); - } // end if type - - Monitor *monitor = new Monitor( - id, - name, - server_id, - storage_id, - (int)function, - enabled, - linked_monitors, - camera, - orientation, - deinterlacing, - decoder_hwaccel_name, - decoder_hwaccel_device, - savejpegs, - videowriter, - encoderparams, - record_audio, - event_prefix, - label_format, - label_coord, - label_size, - image_buffer_count, - warmup_count, - pre_event_count, - post_event_count, - stream_replay_buffer, - alarm_frame_count, - section_length, - min_section_length, - frame_skip, - motion_frame_skip, - capture_max_fps, - analysis_fps, - analysis_update_delay, - capture_delay, - alarm_capture_delay, - fps_report_interval, - ref_blend_perc, - alarm_ref_blend_perc, - track_motion, - signal_check_points, - signal_check_color, - embed_exif, - purpose, - 0, - 0 - ); - camera->setMonitor(monitor); - Zone **zones = 0; - int n_zones = Zone::Load(monitor, zones); - monitor->AddZones(n_zones, zones); - monitor->AddPrivacyBitmask(zones); - Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones); - return monitor; -} // end Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) - -Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) { - std::string sql = load_monitor_sql + stringtf(" WHERE `Id`=%d", p_id); - - zmDbRow dbrow; - if ( ! dbrow.fetch(sql.c_str()) ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - return nullptr; - } - Monitor *monitor = Monitor::Load(dbrow.mysql_row(), load_zones, purpose); - - return monitor; -} // end Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) - /* Returns 0 on success, even if no new images are available (transient error) * Returns -1 on failure. */ int Monitor::Capture() { - static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image - int captureResult; + unsigned int index = image_count % image_buffer_count; - unsigned int index = image_count%image_buffer_count; - Image* capture_image = image_buffer[index].image; + ZMPacket *packet = new ZMPacket(); + packet->timestamp = new struct timeval; + packet->image_index = image_count; + gettimeofday(packet->timestamp, nullptr); + shared_data->zmc_heartbeat_time = packet->timestamp->tv_sec; - unsigned int deinterlacing_value = deinterlacing & 0xff; + int captureResult = camera->Capture(*packet); + Debug(4, "Back from capture result=%d image count %d", captureResult, image_count); - if ( deinterlacing_value == 4 ) { - if ( FirstCapture != 1 ) { - /* Copy the next image into the shared memory */ - capture_image->CopyBuffer(*(next_buffer.image)); - } - - /* Capture a new next image */ - - //Check if FFMPEG camera - // Icon: I don't think we can support de-interlacing on ffmpeg input.... most of the time it will be h264 or mpeg4 - if ( ( videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { - captureResult = camera->CaptureAndRecord(*(next_buffer.image), - video_store_data->recording, - video_store_data->event_file ); - } else { - captureResult = camera->Capture(*(next_buffer.image)); - } - - if ( FirstCapture ) { - FirstCapture = 0; - return 0; - } - - } else { - //Check if FFMPEG camera - if ( (videowriter == H264PASSTHROUGH) && camera->SupportsNativeVideo() ) { - //Warning("ZMC: Recording: %d", video_store_data->recording); - // Should return -1 on error, like loss of signal. Should return 0 if ok but no video frame. > 0 for received a frame. - captureResult = camera->CaptureAndRecord( - *capture_image, - video_store_data->recording, - video_store_data->event_file - ); - } else { - /* Capture directly into image buffer, avoiding the need to memcpy() */ - captureResult = camera->Capture(*capture_image); - } - } // end if deinterlacing or not - - if ( captureResult < 0 ) { - Info("Return from Capture (%d), signal loss", captureResult); - // Tell zma to end the event. zma will reset TRIGGER - trigger_data->trigger_state = TRIGGER_OFF; - // Unable to capture image for temporary reason + if (captureResult < 0) { + Debug(2, "failed capture"); + // Unable to capture image // Fake a signal loss image + // Not sure what to do here. We will close monitor and kill analysis_thread but what about rtsp server? Rgb signalcolor; - signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ + /* HTML colour code is actually BGR in memory, we want RGB */ + signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); + Image *capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); capture_image->Fill(signalcolor); - } else if ( captureResult > 0 ) { - Debug(4, "Return from Capture (%d)", captureResult); - - /* Deinterlacing */ - if ( deinterlacing_value == 1 ) { - capture_image->Deinterlace_Discard(); - } else if ( deinterlacing_value == 2 ) { - capture_image->Deinterlace_Linear(); - } else if ( deinterlacing_value == 3 ) { - capture_image->Deinterlace_Blend(); - } else if ( deinterlacing_value == 4 ) { - capture_image->Deinterlace_4Field(next_buffer.image, (deinterlacing>>8)&0xff); - } else if ( deinterlacing_value == 5 ) { - capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); - } - - if ( orientation != ROTATE_0 ) { - switch ( orientation ) { - case ROTATE_0 : - // No action required - break; - case ROTATE_90 : - case ROTATE_180 : - case ROTATE_270 : - capture_image->Rotate((orientation-1)*90); - break; - case FLIP_HORI : - case FLIP_VERT : - capture_image->Flip(orientation==FLIP_HORI); - break; - } - } // end if have rotation - - if ( capture_image->Size() > camera->ImageSize() ) { - Error("Captured image %d does not match expected size %d check width, height and colour depth", - capture_image->Size(), camera->ImageSize() ); - return -1; - } - - if ( (index == shared_data->last_read_index) && (function > MONITOR) ) { - Warning("Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", - index, image_count ); - time_t now = time(nullptr); - double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec); - time_t last_read_delta = now - shared_data->last_read_time; - if ( last_read_delta > (image_buffer_count/approxFps) ) { - Warning("Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta); - shared_data->last_read_index = image_buffer_count; - } - } // end if overrun - - if ( privacy_bitmask ) - capture_image->MaskPrivacy(privacy_bitmask); - - // Might be able to remove this call, when we start passing around ZMPackets, which will already have a timestamp - gettimeofday(image_buffer[index].timestamp, nullptr); - if ( config.timestamp_on_capture ) { - TimestampImage(capture_image, image_buffer[index].timestamp); - } - // Maybe we don't need to do this on all camera types - shared_data->signal = signal_check_points ? CheckSignal(capture_image) : true; + shared_data->signal = false; shared_data->last_write_index = index; shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; + image_buffer[index].image->Assign(*capture_image); + *(image_buffer[index].timestamp) = *(packet->timestamp); + delete capture_image; + image_count++; + delete packet; + // What about timestamping it? + // Don't want to do analysis on it, but we won't due to signal + return -1; + } else if ( captureResult > 0 ) { + shared_data->signal = true; // Assume if getting packets that we are getting something useful. CheckSignalPoints can correct this later. + // If we captured, let's assume signal, Decode will detect further + if (!decoding_enabled) { + shared_data->last_write_index = index; + shared_data->last_write_time = packet->timestamp->tv_sec; + } + Debug(2, "Have packet stream_index:%d ?= videostream_id: %d q.vpktcount %d event? %d image_count %d", + packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ), image_count ); + + if (packet->codec_type == AVMEDIA_TYPE_VIDEO) { + packet->packet.stream_index = video_stream_id; // Convert to packetQueue's index + if (video_fifo) { + if ( packet->keyframe ) { + // avcodec strips out important nals that describe the stream and + // stick them in extradata. Need to send them along with keyframes +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVStream *stream = camera->getVideoStream(); + video_fifo->write( + static_cast(stream->codecpar->extradata), + stream->codecpar->extradata_size, + packet->pts); +#endif + } + video_fifo->writePacket(*packet); + } + } else if (packet->codec_type == AVMEDIA_TYPE_AUDIO) { + if (audio_fifo) + audio_fifo->writePacket(*packet); + + // Only queue if we have some video packets in there. Should push this logic into packetqueue + if (record_audio and (packetqueue.packet_count(video_stream_id) or event)) { + packet->image_index=-1; + Debug(2, "Queueing audio packet"); + packet->packet.stream_index = audio_stream_id; // Convert to packetQueue's index + packetqueue.queuePacket(packet); + } else { + Debug(4, "Not Queueing audio packet"); + delete packet; + } + // Don't update last_write_index because that is used for live streaming + //shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; + return 1; + } else { + Debug(1, "Unknown codec type %d", packet->codec_type); + delete packet; + return 1; + } // end if audio image_count++; - if ( image_count && fps_report_interval && ( (!(image_count%fps_report_interval)) || image_count < 5 ) ) { - time_t now = image_buffer[index].timestamp->tv_sec; - // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. - if ( now != last_fps_time ) { - // # of images per interval / the amount of time it took - double new_fps = double(image_count%fps_report_interval?image_count:fps_report_interval)/(now-last_fps_time); - unsigned int new_camera_bytes = camera->Bytes(); - unsigned int new_capture_bandwidth = (new_camera_bytes - last_camera_bytes)/(now-last_fps_time); - last_camera_bytes = new_camera_bytes; - //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); - //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); - Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", - name, image_count, new_fps, new_capture_bandwidth); - last_fps_time = now; - fps = new_fps; - db_mutex.lock(); - static char sql[ZM_SQL_SML_BUFSIZ]; - // The reason we update the Status as well is because if mysql restarts, the Monitor_Status table is lost, - // and nothing else will update the status until zmc restarts. Since we are successfully capturing we can - // assume that we are connected - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,CaptureFPS,CaptureBandwidth,Status) " - "VALUES (%d, %.2lf, %u, 'Connected') ON DUPLICATE KEY UPDATE " - "CaptureFPS = %.2lf, CaptureBandwidth=%u, Status='Connected'", - id, fps, new_capture_bandwidth, fps, new_capture_bandwidth); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); - Debug(4,sql); - } // end if time has changed since last update - } // end if it might be time to report the fps - } // end if captureResult + // Will only be queued if there are iterators allocated in the queue. + if ( !packetqueue.queuePacket(packet) ) { + delete packet; + } + UpdateCaptureFPS(); + } else { // result == 0 + // Question is, do we update last_write_index etc? + delete packet; + return 0; + } // end if result // Icon: I'm not sure these should be here. They have nothing to do with capturing if ( shared_data->action & GET_SETTINGS ) { @@ -2595,7 +2626,144 @@ int Monitor::Capture() { shared_data->action &= ~SET_SETTINGS; } return captureResult; -} // end int Monitor::Capture +} // end Monitor::Capture + +bool Monitor::Decode() { + ZMLockedPacket *packet_lock = packetqueue.get_packet(decoder_it); + if (!packet_lock) return false; + ZMPacket *packet = packet_lock->packet_; + packetqueue.increment_it(decoder_it); + if (packet->codec_type != AVMEDIA_TYPE_VIDEO) { + Debug(4, "Not video"); + packetqueue.unlock(packet_lock); + return true; // Don't need decode + } + + if ((!packet->image) and packet->packet.size and !packet->in_frame) { + // Allocate the image first so that it can be used by hwaccel + // We don't actually care about camera colours, pixel order etc. We care about the desired settings + // + //capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); + int ret = packet->decode(camera->getVideoCodecContext()); + if (ret > 0) { + if (packet->in_frame and !packet->image) { + packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); + AVFrame *input_frame = packet->in_frame; + if (!dest_frame) dest_frame = zm_av_frame_alloc(); + + if (!convert_context) { + AVPixelFormat imagePixFormat = (AVPixelFormat)(packet->image->AVPixFormat()); + + convert_context = sws_getContext( + input_frame->width, + input_frame->height, + (AVPixelFormat)input_frame->format, + camera_width, camera_height, + imagePixFormat, SWS_BICUBIC, + nullptr, nullptr, nullptr); + if (convert_context == nullptr) { + Error("Unable to create conversion context from %s to %s", + av_get_pix_fmt_name((AVPixelFormat)input_frame->format), + av_get_pix_fmt_name(imagePixFormat) + ); + delete packet->image; + packet->image = nullptr; + } else { + Debug(1, "Setup conversion context for %dx%d %s to %dx%d %s", + input_frame->width, input_frame->height, + av_get_pix_fmt_name((AVPixelFormat)input_frame->format), + camera_width, camera_height, + av_get_pix_fmt_name(imagePixFormat) + ); + } + } + if (convert_context) { + if (!packet->image->Assign(packet->in_frame, convert_context, dest_frame)) { + delete packet->image; + packet->image = nullptr; + } + } // end if have convert_context + } // end if need transfer to image + } else { + Debug(1, "No packet.size(%d) or packet->in_frame(%p). Not decoding", packet->packet.size, packet->in_frame); + } + } // end if need_decoding + Image* capture_image = nullptr; + unsigned int index = image_count % image_buffer_count; + + if (packet->image) { + capture_image = packet->image; + + /* Deinterlacing */ + if (deinterlacing_value) { + Debug(1, "Doing deinterlacing"); + if (deinterlacing_value == 1) { + capture_image->Deinterlace_Discard(); + } else if (deinterlacing_value == 2) { + capture_image->Deinterlace_Linear(); + } else if (deinterlacing_value == 3) { + capture_image->Deinterlace_Blend(); + } else if (deinterlacing_value == 4) { + ZMLockedPacket *deinterlace_packet_lock = nullptr; + while (!zm_terminate) { + ZMLockedPacket *second_packet_lock = packetqueue.get_packet(decoder_it); + if (!second_packet_lock) { + packetqueue.unlock(packet_lock); + return false; + } + if (second_packet_lock->packet_->codec_type == packet->codec_type) { + deinterlace_packet_lock = second_packet_lock; + break; + } + packetqueue.unlock(second_packet_lock); + packetqueue.increment_it(decoder_it); + } + if (zm_terminate) return false; + capture_image->Deinterlace_4Field(deinterlace_packet_lock->packet_->image, (deinterlacing>>8)&0xff); + packetqueue.unlock(deinterlace_packet_lock); + } else if (deinterlacing_value == 5) { + capture_image->Deinterlace_Blend_CustomRatio((deinterlacing>>8)&0xff); + } + } + + if ( orientation != ROTATE_0 ) { + Debug(2, "Doing rotation"); + switch ( orientation ) { + case ROTATE_0 : + // No action required + break; + case ROTATE_90 : + case ROTATE_180 : + case ROTATE_270 : + capture_image->Rotate((orientation-1)*90); + break; + case FLIP_HORI : + case FLIP_VERT : + capture_image->Flip(orientation==FLIP_HORI); + break; + } + } // end if have rotation + + if (privacy_bitmask) { + Debug(3, "Applying privacy"); + capture_image->MaskPrivacy(privacy_bitmask); + } + + if (config.timestamp_on_capture) { + Debug(3, "Timestampprivacy"); + TimestampImage(packet->image, packet->timestamp); + } + + image_buffer[index].image->Assign(*(packet->image)); + *(image_buffer[index].timestamp) = *(packet->timestamp); + } // end if have image + packet->decoded = true; + shared_data->signal = ( capture_image and signal_check_points ) ? CheckSignal(capture_image) : true; + shared_data->last_write_index = index; + shared_data->last_write_time = packet->timestamp->tv_sec; + packetqueue.unlock(packet_lock); + return true; +} // end bool Monitor::Decode() void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) const { if ( !label_format[0] ) @@ -2603,8 +2771,8 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con // Expand the strftime macros first char label_time_text[256]; - strftime(label_time_text, sizeof(label_time_text), label_format, localtime(&ts_time->tv_sec)); - + tm ts_tm = {}; + strftime(label_time_text, sizeof(label_time_text), label_format.c_str(), localtime_r(&ts_time->tv_sec, &ts_tm)); char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; @@ -2613,7 +2781,7 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con bool found_macro = false; switch ( *(s_ptr+1) ) { case 'N' : - d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name); + d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name.c_str()); found_macro = true; break; case 'Q' : @@ -2633,219 +2801,208 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con *d_ptr++ = *s_ptr++; } // end while *d_ptr = '\0'; - ts_image->Annotate( label_text, label_coord, label_size ); + Debug(2, "annotating %s", label_text); + ts_image->Annotate(label_text, label_coord, label_size); + Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage -bool Monitor::closeEvent() { - if ( !event ) - return false; +void Monitor::closeEvent() { + if (!event) return; - if ( function == RECORD || function == MOCORD ) { - //FIXME Is this neccessary? ENdTime should be set in the destructor - gettimeofday(&(event->EndTime()), nullptr); + if ( close_event_thread.joinable() ) { + Debug(1, "close event thread is joinable"); + close_event_thread.join(); + } else { + Debug(1, "close event thread is not joinable"); } - if ( event_delete_thread ) { - event_delete_thread->join(); - delete event_delete_thread; - event_delete_thread = nullptr; - } -#if 0 - event_delete_thread = new std::thread([](Event *event) { - Event * e = event; - event = nullptr; - delete e; - e = nullptr; - }, event); -#else - delete event; + Debug(1, "Starting thread to close event"); + close_event_thread = std::thread([](Event *e){ delete e; }, event); + Debug(1, "Nulling event"); event = nullptr; -#endif - video_store_data->recording = (struct timeval){0}; - return true; + if (shared_data) video_store_data->recording = {}; } // end bool Monitor::closeEvent() unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zoneSet) { bool alarm = false; unsigned int score = 0; - if ( n_zones <= 0 ) return alarm; + if (zones.empty()) { + Warning("No zones to check!"); + return alarm; + } ref_image.Delta(comp_image, &delta_image); - if ( config.record_diag_images ) { - ref_image.WriteJpeg(diag_path_r.c_str(), config.record_diag_images_fifo); - delta_image.WriteJpeg(diag_path_d.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + ref_image.WriteJpeg(diag_path_ref.c_str(), config.record_diag_images_fifo); + delta_image.WriteJpeg(diag_path_delta.c_str(), config.record_diag_images_fifo); } // Blank out all exclusion zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; + for (Zone &zone : zones) { // need previous alarmed state for preclusive zone, so don't clear just yet - if ( !zone->IsPreclusive() ) - zone->ClearAlarm(); - if ( !zone->IsInactive() ) { + if (!zone.IsPreclusive()) + zone.ClearAlarm(); + if (!zone.IsInactive()) continue; - } - Debug(3, "Blanking inactive zone %s", zone->Label()); - delta_image.Fill(RGB_BLACK, zone->GetPolygon()); + Debug(3, "Blanking inactive zone %s", zone.Label()); + delta_image.Fill(kRGBBlack, zone.GetPolygon()); } // end foreach zone // Check preclusive zones first - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; - if ( !zone->IsPreclusive() ) { + for (Zone &zone : zones) { + if (!zone.IsPreclusive()) continue; - } - int old_zone_score = zone->Score(); - bool old_zone_alarmed = zone->Alarmed(); + int old_zone_score = zone.Score(); + bool old_zone_alarmed = zone.Alarmed(); Debug(3, "Checking preclusive zone %s - old score: %d, state: %s", - zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet"); - if ( zone->CheckAlarms(&delta_image) ) { + zone.Label(),old_zone_score, zone.Alarmed()?"alarmed":"quiet"); + if (zone.CheckAlarms(&delta_image)) { alarm = true; - score += zone->Score(); - zone->SetAlarm(); - Debug(3, "Zone is alarmed, zone score = %d", zone->Score()); - zoneSet.insert(zone->Label()); - //zone->ResetStats(); + score += zone.Score(); + zone.SetAlarm(); + Debug(3, "Zone is alarmed, zone score = %d", zone.Score()); + zoneSet.insert(zone.Label()); } else { // check if end of alarm - if ( old_zone_alarmed ) { + if (old_zone_alarmed) { Debug(3, "Preclusive Zone %s alarm Ends. Previous score: %d", - zone->Label(), old_zone_score); - if ( old_zone_score > 0 ) { - zone->SetExtendAlarmCount(zone->GetExtendAlarmFrames()); + zone.Label(), old_zone_score); + if (old_zone_score > 0) { + zone.SetExtendAlarmCount(zone.GetExtendAlarmFrames()); } - if ( zone->CheckExtendAlarmCount() ) { + if (zone.CheckExtendAlarmCount()) { alarm = true; - zone->SetAlarm(); + zone.SetAlarm(); } else { - zone->ClearAlarm(); + zone.ClearAlarm(); } - } + } // end if zone WAS alarmed } // end if CheckAlarms } // end foreach zone Coord alarm_centre; int top_score = -1; - if ( alarm ) { + if (alarm) { alarm = false; score = 0; } else { // Find all alarm pixels in active zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; - if ( !zone->IsActive() || zone->IsPreclusive()) { + for (Zone &zone : zones) { + if (!zone.IsActive() || zone.IsPreclusive()) { continue; } - Debug(3, "Checking active zone %s", zone->Label()); - if ( zone->CheckAlarms(&delta_image) ) { + Debug(3, "Checking active zone %s", zone.Label()); + if (zone.CheckAlarms(&delta_image)) { alarm = true; - score += zone->Score(); - zone->SetAlarm(); - Debug(3, "Zone is alarmed, zone score = %d", zone->Score()); - zoneSet.insert(zone->Label()); - if ( config.opt_control && track_motion ) { - if ( (int)zone->Score() > top_score ) { - top_score = zone->Score(); - alarm_centre = zone->GetAlarmCentre(); + score += zone.Score(); + zone.SetAlarm(); + Debug(3, "Zone is alarmed, zone score = %d", zone.Score()); + zoneSet.insert(zone.Label()); + if (config.opt_control && track_motion) { + if ((int)zone.Score() > top_score) { + top_score = zone.Score(); + alarm_centre = zone.GetAlarmCentre(); } } } } // end foreach zone - if ( alarm ) { - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; - // Wasn't this zone already checked above? - if ( !zone->IsInclusive() ) { + if (alarm) { + for (Zone &zone : zones) { + if (!zone.IsInclusive()) { continue; } - Debug(3, "Checking inclusive zone %s", zone->Label()); - if ( zone->CheckAlarms(&delta_image) ) { - alarm = true; - score += zone->Score(); - zone->SetAlarm(); - Debug(3, "Zone is alarmed, zone score = %d", zone->Score()); - zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) { - if ( zone->Score() > (unsigned int)top_score ) { - top_score = zone->Score(); - alarm_centre = zone->GetAlarmCentre(); + Debug(3, "Checking inclusive zone %s", zone.Label()); + if (zone.CheckAlarms(&delta_image)) { + score += zone.Score(); + zone.SetAlarm(); + Debug(3, "Zone is alarmed, zone score = %d", zone.Score()); + zoneSet.insert(zone.Label()); + if (config.opt_control && track_motion) { + if (zone.Score() > (unsigned int)top_score) { + top_score = zone.Score(); + alarm_centre = zone.GetAlarmCentre(); } } } // end if CheckAlarm } // end foreach zone } else { // Find all alarm pixels in exclusive zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { - Zone *zone = zones[n_zone]; - if ( !zone->IsExclusive() ) { + for (Zone &zone : zones) { + if (!zone.IsExclusive()) { continue; } - Debug(3, "Checking exclusive zone %s", zone->Label()); - if ( zone->CheckAlarms(&delta_image) ) { + Debug(3, "Checking exclusive zone %s", zone.Label()); + if (zone.CheckAlarms(&delta_image)) { alarm = true; - score += zone->Score(); - zone->SetAlarm(); - Debug(3, "Zone is alarmed, zone score = %d", zone->Score()); - zoneSet.insert(zone->Label()); + score += zone.Score(); + zone.SetAlarm(); + Debug(3, "Zone is alarmed, zone score = %d", zone.Score()); + zoneSet.insert(zone.Label()); } } // end foreach zone } // end if alarm or not } // end if alarm - if ( top_score > 0 ) { + if (top_score > 0) { shared_data->alarm_x = alarm_centre.X(); shared_data->alarm_y = alarm_centre.Y(); Info("Got alarm centre at %d,%d, at count %d", - shared_data->alarm_x, shared_data->alarm_y, image_count); + shared_data->alarm_x, shared_data->alarm_y, analysis_image_count); } else { shared_data->alarm_x = shared_data->alarm_y = -1; } // This is a small and innocent hack to prevent scores of 0 being returned in alarm state return score ? score : alarm; -} // end MotionDetect +} // end DetectMotion +// TODO: Move the camera specific things to the camera classes and avoid these casts. bool Monitor::DumpSettings(char *output, bool verbose) { output[0] = 0; - sprintf( output+strlen(output), "Id : %d\n", id ); - sprintf( output+strlen(output), "Name : %s\n", name ); + sprintf( output+strlen(output), "Id : %u\n", id ); + sprintf( output+strlen(output), "Name : %s\n", name.c_str() ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); #if ZM_HAS_V4L if ( camera->IsLocal() ) { - sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() ); - sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() ); - sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() ); + LocalCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Device : %s\n", cam->Device().c_str() ); + sprintf( output+strlen(output), "Channel : %d\n", cam->Channel() ); + sprintf( output+strlen(output), "Standard : %d\n", cam->Standard() ); } else #endif // ZM_HAS_V4L if ( camera->IsRemote() ) { - sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() ); - sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() ); - sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() ); - sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() ); + RemoteCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Protocol : %s\n", cam->Protocol().c_str() ); + sprintf( output+strlen(output), "Host : %s\n", cam->Host().c_str() ); + sprintf( output+strlen(output), "Port : %s\n", cam->Port().c_str() ); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } else if ( camera->IsFile() ) { - sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() ); + FileCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Path : %s\n", cam->Path() ); } #if HAVE_LIBAVFORMAT else if ( camera->IsFfmpeg() ) { - sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() ); + FfmpegCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } #endif // HAVE_LIBAVFORMAT - sprintf( output+strlen(output), "Width : %d\n", camera->Width() ); - sprintf( output+strlen(output), "Height : %d\n", camera->Height() ); + sprintf( output+strlen(output), "Width : %u\n", camera->Width() ); + sprintf( output+strlen(output), "Height : %u\n", camera->Height() ); #if ZM_HAS_V4L if ( camera->IsLocal() ) { - sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() ); + LocalCamera* cam = static_cast(camera.get()); + sprintf( output+strlen(output), "Palette : %d\n", cam->Palette() ); } #endif // ZM_HAS_V4L - sprintf(output+strlen(output), "Colours : %d\n", camera->Colours() ); - sprintf(output+strlen(output), "Subpixel Order : %d\n", camera->SubpixelOrder() ); - sprintf(output+strlen(output), "Event Prefix : %s\n", event_prefix ); - sprintf(output+strlen(output), "Label Format : %s\n", label_format ); + sprintf(output+strlen(output), "Colours : %u\n", camera->Colours() ); + sprintf(output+strlen(output), "Subpixel Order : %u\n", camera->SubpixelOrder() ); + sprintf(output+strlen(output), "Event Prefix : %s\n", event_prefix.c_str() ); + sprintf(output+strlen(output), "Label Format : %s\n", label_format.c_str() ); sprintf(output+strlen(output), "Label Coord : %d,%d\n", label_coord.X(), label_coord.Y() ); sprintf(output+strlen(output), "Label Size : %d\n", label_size ); sprintf(output+strlen(output), "Image Buffer Count : %d\n", image_buffer_count ); @@ -2869,23 +3026,173 @@ bool Monitor::DumpSettings(char *output, bool verbose) { function==MOCORD?"Continuous Record with Motion Detection":( function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown" )))))); - sprintf(output+strlen(output), "Zones : %d\n", n_zones ); - for ( int i = 0; i < n_zones; i++ ) { - zones[i]->DumpSettings(output+strlen(output), verbose); + sprintf(output+strlen(output), "Zones : %lu\n", zones.size()); + for (const Zone &zone : zones) { + zone.DumpSettings(output+strlen(output), verbose); } + sprintf(output+strlen(output), "Recording Enabled? %s\n", enabled ? "enabled" : "disabled"); + sprintf(output+strlen(output), "Events Enabled (!TRIGGER_OFF)? %s\n", trigger_data->trigger_state == TRIGGER_OFF ? "disabled" : "enabled"); + sprintf(output+strlen(output), "Motion Detection Enabled? %s\n", shared_data->active ? "enabled" : "disabled"); return true; } // bool Monitor::DumpSettings(char *output, bool verbose) -unsigned int Monitor::Colours() const { return camera->Colours(); } -unsigned int Monitor::SubpixelOrder() const { return camera->SubpixelOrder(); } -int Monitor::PrimeCapture() const { return camera->PrimeCapture(); } +unsigned int Monitor::Colours() const { return camera ? camera->Colours() : colours; } +unsigned int Monitor::SubpixelOrder() const { return camera ? camera->SubpixelOrder() : 0; } + +int Monitor::PrimeCapture() { + int ret = camera->PrimeCapture(); + if (ret <= 0) return ret; + + if ( -1 != camera->getVideoStreamId() ) { + video_stream_id = packetqueue.addStream(); + } + + if ( -1 != camera->getAudioStreamId() ) { + audio_stream_id = packetqueue.addStream(); + packetqueue.addStream(); + shared_data->audio_frequency = camera->getFrequency(); + shared_data->audio_channels = camera->getChannels(); + } + + Debug(2, "Video stream id is %d, audio is %d, minimum_packets to keep in buffer %d", + video_stream_id, audio_stream_id, pre_event_count); + + if (rtsp_server) { + if (video_stream_id >= 0) { + AVStream *videoStream = camera->getVideoStream(); + snprintf(shared_data->video_fifo_path, sizeof(shared_data->video_fifo_path)-1, "%s/video_fifo_%u.%s", + staticConfig.PATH_SOCKS.c_str(), + id, +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_get_name(videoStream->codecpar->codec_id) +#else + avcodec_get_name(videoStream->codec->codec_id) +#endif + ); + video_fifo = new Fifo(shared_data->video_fifo_path, true); + } + if (record_audio and (audio_stream_id >= 0)) { + AVStream *audioStream = camera->getAudioStream(); + if (audioStream && CODEC(audioStream)) { + snprintf(shared_data->audio_fifo_path, sizeof(shared_data->audio_fifo_path)-1, "%s/audio_fifo_%u.%s", + staticConfig.PATH_SOCKS.c_str(), id, +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_get_name(audioStream->codecpar->codec_id) +#else + avcodec_get_name(audioStream->codec->codec_id) +#endif + ); + audio_fifo = new Fifo(shared_data->audio_fifo_path, true); + } else { + Warning("No audioStream %p or codec?", audioStream); + } + } + } // end if rtsp_server + + if (decoding_enabled) { + if (!decoder_it) decoder_it = packetqueue.get_video_it(false); + if (!decoder) { + Debug(1, "Creating decoder thread"); + decoder = ZM::make_unique(this); + } else { + Debug(1, "Restartg decoder thread"); + decoder->Start(); + } + } + Debug(1, "Done restarting decoder"); + if (!analysis_it) { + Debug(1, "getting analysis_it"); + analysis_it = packetqueue.get_video_it(false); + } else { + Debug(1, "haveing analysis_it"); + } + if (!analysis_thread) { + Debug(1, "Starting an analysis thread for monitor (%d)", id); + analysis_thread = ZM::make_unique(this); + } else { + Debug(1, "Restarting analysis thread for monitor (%d)", id); + analysis_thread->Start(); + } + return ret; +} // end int Monitor::PrimeCapture() + int Monitor::PreCapture() const { return camera->PreCapture(); } -int Monitor::PostCapture() const { return camera->PostCapture() ; } -int Monitor::Close() { return camera->Close(); }; +int Monitor::PostCapture() const { return camera->PostCapture(); } +int Monitor::Close() { + if (close_event_thread.joinable()) { + close_event_thread.join(); + } + // Because the stream indexes may change we have to clear out the packetqueue + if (decoder) { + decoder->Stop(); + } + if (analysis_thread) { + analysis_thread->Stop(); + } + packetqueue.clear(); + if (audio_fifo) { + delete audio_fifo; + audio_fifo = nullptr; + } + if (video_fifo) { + delete video_fifo; + video_fifo = nullptr; + } + + std::lock_guard lck(event_mutex); + if (event) { + Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id()); + closeEvent(); + } + if (camera) camera->Close(); + return 1; +} + Monitor::Orientation Monitor::getOrientation() const { return orientation; } -Monitor::Snapshot *Monitor::getSnapshot() const { - return &image_buffer[ shared_data->last_write_index%image_buffer_count ]; +// Wait for camera to get an image, and then assign it as the base reference image. +// So this should be done as the first task in the analysis thread startup. +// This function is deprecated. +void Monitor::get_ref_image() { + ZMLockedPacket *snap_lock = nullptr; + + if ( !analysis_it ) + analysis_it = packetqueue.get_video_it(true); + + while ( + ( + !( snap_lock = packetqueue.get_packet(analysis_it)) + or + ( snap_lock->packet_->codec_type != AVMEDIA_TYPE_VIDEO ) + or + ! snap_lock->packet_->image + ) + and !zm_terminate) { + + Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%" PRIi64 ")", + shared_data->last_write_index, static_cast(shared_data->last_write_time)); + if ( snap_lock and ! snap_lock->packet_->image ) { + delete snap_lock; + // can't analyse it anyways, incremement + packetqueue.increment_it(analysis_it); + } + //usleep(10000); + } + if ( zm_terminate ) + return; + + ZMPacket *snap = snap_lock->packet_; + Debug(1, "get_ref_image: packet.stream %d ?= video_stream %d, packet image id %d packet image %p", + snap->packet.stream_index, video_stream_id, snap->image_index, snap->image ); + // Might not have been decoded yet FIXME + if ( snap->image ) { + ref_image.Assign(width, height, camera->Colours(), + camera->SubpixelOrder(), snap->image->Buffer(), camera->ImageSize()); + Debug(2, "Have ref image about to unlock"); + } else { + Debug(2, "Have no ref image about to unlock"); + } + delete snap_lock; } std::vector Monitor::Groups() { @@ -2893,7 +3200,7 @@ std::vector Monitor::Groups() { if ( !groups.size() ) { std::string sql = stringtf( "SELECT `Id`, `ParentId`, `Name` FROM `Groups` WHERE `Groups.Id` IN " - "(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)",id); + "(SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=%d)", id); MYSQL_RES *result = zmDbFetch(sql.c_str()); if ( !result ) { Error("Can't load groups: %s", mysql_error(&dbconn)); @@ -2916,7 +3223,7 @@ StringVector Monitor::GroupNames() { StringVector groupnames; for ( Group * g: Groups() ) { groupnames.push_back(std::string(g->Name())); - Debug(1,"Groups: %s", g->Name()); + Debug(1, "Groups: %s", g->Name()); } return groupnames; } // end Monitor::GroupNames() diff --git a/src/zm_monitor.h b/src/zm_monitor.h index e020ba391..0efc8a130 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -1,45 +1,40 @@ // // ZoneMinder Monitor Class Interfaces, $Date$, $Revision$ // Copyright (C) 2001-2008 Philip Coombes -// +// // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -// +// // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -// +// // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// #ifndef ZM_MONITOR_H #define ZM_MONITOR_H -#include -#include -#include - -#include "zm.h" -#include "zm_coord.h" -#include "zm_image.h" -#include "zm_rgb.h" -#include "zm_zone.h" -#include "zm_event.h" -class Monitor; -#include "zm_group.h" +#include "zm_define.h" #include "zm_camera.h" -#include "zm_storage.h" +#include "zm_analysis_thread.h" +#include "zm_decoder_thread.h" +#include "zm_event.h" +#include "zm_fifo.h" +#include "zm_image.h" +#include "zm_packet.h" +#include "zm_packetqueue.h" #include "zm_utils.h" - -#include "zm_image_analyser.h" - +#include #include -#include +#include + +class Group; #define SIGNAL_CAUSE "Signal" #define MOTION_CAUSE "Motion" @@ -75,10 +70,11 @@ public: FFMPEG, LIBVLC, CURL, + NVSOCKET, VNC, } CameraType; - typedef enum { + typedef enum { ROTATE_0=1, ROTATE_90, ROTATE_180, @@ -88,7 +84,23 @@ public: } Orientation; typedef enum { - UNKNOWN, + DEINTERLACE_DISABLED = 0x00000000, + DEINTERLACE_FOUR_FIELD_SOFT = 0x00001E04, + DEINTERLACE_FOUR_FIELD_MEDIUM = 0x00001404, + DEINTERLACE_FOUR_FIELD_HARD = 0x00000A04, + DEINTERLACE_DISCARD = 0x00000001, + DEINTERLACE_LINEAR = 0x00000002, + DEINTERLACE_BLEND = 0x00000003, + DEINTERLACE_BLEND_25 = 0x00000205, + DEINTERLACE_V4L2_TOP = 0x02000000, + DEINTERLACE_V4L2_BOTTOM = 0x03000000, + DEINTERLACE_V4L2_ALTERNATE = 0x07000000, + DEINTERLACE_V4L2_PROGRESSIVE = 0x01000000, + DEINTERLACE_V4L2_INTERLACED = 0x04000000 + } Deinterlace; + + typedef enum { + UNKNOWN=-1, IDLE, PREALARM, ALARM, @@ -98,8 +110,8 @@ public: typedef enum { DISABLED, - X264ENCODE, - H264PASSTHROUGH, + ENCODE, + PASSTHROUGH, } VideoWriter; protected: @@ -109,13 +121,15 @@ protected: typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; - /* sizeof(SharedData) expected to be 340 bytes on 32bit and 64bit */ + /* sizeof(SharedData) expected to be 472 bytes on 32bit and 64bit */ typedef struct { uint32_t size; /* +0 */ - uint32_t last_write_index; /* +4 */ - uint32_t last_read_index; /* +8 */ + int32_t last_write_index; /* +4 */ + int32_t last_read_index; /* +8 */ uint32_t state; /* +12 */ - uint64_t last_event; /* +16 */ + double capture_fps; // Current capturing fps + double analysis_fps; // Current analysis fps + uint64_t last_event_id; /* +16 */ uint32_t action; /* +24 */ int32_t brightness; /* +28 */ int32_t hue; /* +32 */ @@ -128,45 +142,49 @@ protected: uint8_t signal; /* +54 */ uint8_t format; /* +55 */ uint32_t imagesize; /* +56 */ - uint32_t epadding1; /* +60 */ + uint32_t last_frame_score; /* +60 */ + uint32_t audio_frequency; /* +64 */ + uint32_t audio_channels; /* +68 */ /* ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. ** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple ** of 8. Add or delete epadding's to achieve this. */ - union { /* +64 */ + union { /* +72 */ time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */ uint64_t extrapad1; }; - union { /* +72 */ + union { /* +80 */ time_t zmc_heartbeat_time; /* Constantly updated by zmc. Used to determine if the process is alive or hung or dead */ uint64_t extrapad2; }; - union { /* +80 */ - time_t zma_heartbeat_time; /* Constantly updated by zma. Used to determine if the process is alive or hung or dead */ - uint64_t extrapad3; - }; union { /* +88 */ time_t last_write_time; - uint64_t extrapad4; + uint64_t extrapad3; }; union { /* +96 */ time_t last_read_time; - uint64_t extrapad5; + uint64_t extrapad4; }; uint8_t control_state[256]; /* +104 */ char alarm_cause[256]; - + char video_fifo_path[64]; + char audio_fifo_path[64]; + } SharedData; - typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState; + enum TriggerState : uint32 { + TRIGGER_CANCEL, + TRIGGER_ON, + TRIGGER_OFF + }; /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */ typedef struct { uint32_t size; - uint32_t trigger_state; + TriggerState trigger_state; uint32_t trigger_score; uint32_t padding; char trigger_cause[32]; @@ -174,26 +192,15 @@ protected: char trigger_showtext[256]; } TriggerData; - /* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */ - struct Snapshot { - struct timeval *timestamp; - Image *image; - void* padding; - }; - //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat -#if 1 //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit typedef struct { uint32_t size; uint64_t current_event; char event_file[4096]; timeval recording; // used as both bool and a pointer to the timestamp when recording should begin - //uint32_t frameNumber; } VideoStoreData; -#endif // HAVE_LIBAVFORMAT - class MonitorLink { protected: unsigned int id; @@ -216,24 +223,20 @@ protected: volatile VideoStoreData *video_store_data; int last_state; - uint64_t last_event; + uint64_t last_event_id; public: MonitorLink(unsigned int p_id, const char *p_name); ~MonitorLink(); - inline unsigned int Id() const { - return id; - } - inline const char *Name() const { - return name; - } + inline unsigned int Id() const { return id; } + inline const char *Name() const { return name; } - inline bool isConnected() const { - return connected && shared_data->valid; - } - inline time_t getLastConnectTime() const { - return last_connect_time; + inline bool isConnected() const { return connected && shared_data->valid; } + inline time_t getLastConnectTime() const { return last_connect_time; } + + inline uint32_t lastFrameScore() { + return shared_data->last_frame_score; } bool connect(); @@ -247,87 +250,114 @@ protected: protected: // These are read from the DB and thereafter remain unchanged unsigned int id; - char name[64]; + std::string name; unsigned int server_id; // Id of the Server object unsigned int storage_id; // Id of the Storage Object, which currently will just provide a path, but in future may do more. CameraType type; Function function; // What the monitor is doing bool enabled; // Whether the monitor is enabled or asleep + bool decoding_enabled; // Whether the monitor will decode h264/h265 packets + + std::string protocol; + std::string method; + std::string options; + std::string host; + std::string port; + std::string user; + std::string pass; + std::string path; + std::string second_path; + + std::string device; + int palette; + int channel; + int format; + + unsigned int camera_width; + unsigned int camera_height; unsigned int width; // Normally the same as the camera, but not if partly rotated unsigned int height; // Normally the same as the camera, but not if partly rotated bool v4l_multi_buffer; unsigned int v4l_captures_per_frame; Orientation orientation; // Whether the image has to be rotated at all unsigned int deinterlacing; - bool videoRecording; + unsigned int deinterlacing_value; std::string decoder_hwaccel_name; std::string decoder_hwaccel_device; + bool videoRecording; + bool rtsp_describe; - int savejpegs; - VideoWriter videowriter; - std::string encoderparams; - std::vector encoderparamsvec; - bool record_audio; // Whether to store the audio that we receive + int savejpegs; + int colours; + VideoWriter videowriter; + std::string encoderparams; + int output_codec; + std::string encoder; + std::string output_container; + _AVPIXELFORMAT imagePixFormat; + bool record_audio; // Whether to store the audio that we receive - int brightness; // The statically saved brightness of the camera - int contrast; // The statically saved contrast of the camera - int hue; // The statically saved hue of the camera - int colour; // The statically saved colour of the camera - char event_prefix[64]; // The prefix applied to event names as they are created - char label_format[64]; // The format of the timestamp on the images - Coord label_coord; // The coordinates of the timestamp on the images - int label_size; // Size of the timestamp on the images - int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count - int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate, - // value is pre_event_count + alarm_frame_count - 1 - int warmup_count; // How many images to process before looking for events - int pre_event_count; // How many images to hold and prepend to an alarm event - int post_event_count; // How many unalarmed images must occur before the alarm state is reset - struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 ) - int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now - int section_length; // How long events should last in continuous modes - int min_section_length; // Minimum event length when using event_close_mode == ALARM - bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor - int frame_skip; // How many frames to skip in continuous modes - int motion_frame_skip; // How many frames to skip in motion detection - double capture_max_fps; // Target Capture FPS - double analysis_fps; // Target framerate for video analysis + + int brightness; // The statically saved brightness of the camera + int contrast; // The statically saved contrast of the camera + int hue; // The statically saved hue of the camera + int colour; // The statically saved colour of the camera + + std::string event_prefix; // The prefix applied to event names as they are created + std::string label_format; // The format of the timestamp on the images + Coord label_coord; // The coordinates of the timestamp on the images + int label_size; // Size of the timestamp on the images + int32_t image_buffer_count; // Size of circular image buffer, kept in /dev/shm + int32_t max_image_buffer_count; // Max # of video packets to keep in packet queue + int warmup_count; // How many images to process before looking for events + int pre_event_count; // How many images to hold and prepend to an alarm event + int post_event_count; // How many unalarmed images must occur before the alarm state is reset + int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now + int section_length; // How long events should last in continuous modes + int min_section_length; // Minimum event length when using event_close_mode == ALARM + bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor + int frame_skip; // How many frames to skip in continuous modes + int motion_frame_skip; // How many frames to skip in motion detection + double analysis_fps_limit; // Target framerate for video analysis unsigned int analysis_update_delay; // How long we wait before updating analysis parameters - int capture_delay; // How long we wait between capture frames - int alarm_capture_delay; // How long we wait between capture frames when in alarm state - int alarm_frame_count; // How many alarm frames are required before an event is triggered - int fps_report_interval; // How many images should be captured/processed between reporting the current FPS - int ref_blend_perc; // Percentage of new image going into reference image. - int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. - bool track_motion; // Whether this monitor tries to track detected motion - int signal_check_points; // Number of points in the image to check for signal - Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected - bool embed_exif; // Whether to embed Exif data into each image frame or not + int capture_delay; // How long we wait between capture frames + int alarm_capture_delay; // How long we wait between capture frames when in alarm state + int alarm_frame_count; // How many alarm frames are required before an event is triggered + int alert_to_alarm_frame_count; // How many alarm frames (consecutive score frames) are required to return alarm from alert + // value for now is the same number configured in alarm_frame_count, maybe getting his own parameter some day + int fps_report_interval; // How many images should be captured/processed between reporting the current FPS + int ref_blend_perc; // Percentage of new image going into reference image. + int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. + bool track_motion; // Whether this monitor tries to track detected motion + int signal_check_points; // Number of points in the image to check for signal + Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected + bool embed_exif; // Whether to embed Exif data into each image frame or not + bool rtsp_server; // Whether to include this monitor as an rtsp server stream + std::string rtsp_streamname; // path in the rtsp url for this monitor + int importance; // Importance of this monitor, affects Connection logging errors. - bool last_signal; - - double fps; - unsigned int last_camera_bytes; - - Image delta_image; - Image ref_image; - Image alarm_image; // Used in creating analysis images, will be initialized in Analysis - Image write_image; // Used when creating snapshot images - std::string diag_path_r; - std::string diag_path_d; + int capture_max_fps; Purpose purpose; // What this monitor has been created to do - int event_count; - int image_count; - int ready_count; - int first_alarm_count; - int last_alarm_count; - int buffer_count; - int prealarm_count; - State state; - time_t start_time; - time_t last_fps_time; - time_t auto_resume_time; + unsigned int last_camera_bytes; + + int event_count; + int image_count; + int last_capture_image_count; // last value of image_count when calculating capture fps + int analysis_image_count; // How many frames have been processed by analysis thread. + int motion_frame_count; // How many frames have had motion detection performed on them. + int last_motion_frame_count; // last value of motion_frame_count when calculating fps + int ready_count; + int first_alarm_count; + int last_alarm_count; + bool last_signal; + int last_section_mod; + int buffer_count; + State state; + time_t start_time; + double last_fps_time; + double last_analysis_fps_time; + time_t auto_resume_time; unsigned int last_motion_score; EventCloseMode event_close_mode; @@ -344,149 +374,165 @@ protected: TriggerData *trigger_data; VideoStoreData *video_store_data; - Snapshot *image_buffer; - Snapshot next_buffer; /* Used by four field deinterlacing */ - Snapshot *pre_event_buffer; + struct timeval *shared_timestamps; + unsigned char *shared_images; + ZMPacket *image_buffer; + ZMPacket next_buffer; /* Used by four field deinterlacing */ - Camera *camera; + int video_stream_id; // will be filled in PrimeCapture + int audio_stream_id; // will be filled in PrimeCapture + Fifo *video_fifo; + Fifo *audio_fifo; + + std::unique_ptr camera; Event *event; + std::mutex event_mutex; Storage *storage; - int n_zones; - Zone **zones; + VideoStore *videoStore; + PacketQueue packetqueue; + packetqueue_iterator *analysis_it; + std::unique_ptr analysis_thread; + packetqueue_iterator *decoder_it; + std::unique_ptr decoder; + AVFrame *dest_frame; // Used by decoding thread doing colorspace conversions + SwsContext *convert_context; + std::thread close_event_thread; - struct timeval **timestamps; - Image **images; + std::vector zones; const unsigned char *privacy_bitmask; - std::thread *event_delete_thread; // Used to close events, but continue processing. int n_linked_monitors; MonitorLink **linked_monitors; std::vector groups; + Image delta_image; + Image ref_image; + Image alarm_image; // Used in creating analysis images, will be initialized in Analysis + Image write_image; // Used when creating snapshot images + std::string diag_path_ref; + std::string diag_path_delta; + + // Used in check signal + uint8_t red_val; + uint8_t green_val; + uint8_t blue_val; + uint8_t grayscale_val; /* 8bit grayscale color */ + Rgb colour_val; /* RGB32 color */ + int usedsubpixorder; + public: + explicit Monitor(); explicit Monitor(unsigned int p_id); -// OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info. -//bool OurCheckAlarms( Zone *zone, const Image *pImage ); - Monitor( - unsigned int p_id, - const char *p_name, - unsigned int p_server_id, - unsigned int p_storage_id, - int p_function, - bool p_enabled, - const char *p_linked_monitors, - Camera *p_camera, - int p_orientation, - unsigned int p_deinterlacing, - const std::string &p_decoder_hwaccel_name, - const std::string &p_decoder_hwaccel_device, - int p_savejpegs, - VideoWriter p_videowriter, - std::string p_encoderparams, - bool p_record_audio, - const char *p_event_prefix, - const char *p_label_format, - const Coord &p_label_coord, - int label_size, - int p_image_buffer_count, - int p_warmup_count, - int p_pre_event_count, - int p_post_event_count, - int p_stream_replay_buffer, - int p_alarm_frame_count, - int p_section_length, - int p_min_section_length, - int p_frame_skip, - int p_motion_frame_skip, - double p_capture_max_fps, - double p_analysis_fps, - unsigned int p_analysis_update_delay, - int p_capture_delay, - int p_alarm_capture_delay, - int p_fps_report_interval, - int p_ref_blend_perc, - int p_alarm_ref_blend_perc, - bool p_track_motion, - int p_signal_check_points, - Rgb p_signal_check_colour, - bool p_embed_exif, - Purpose p_purpose, - int p_n_zones=0, - Zone *p_zones[]=0 - ); ~Monitor(); - void AddZones( int p_n_zones, Zone *p_zones[] ); - void AddPrivacyBitmask( Zone *p_zones[] ); + void AddPrivacyBitmask(); + void LoadCamera(); bool connect(); + bool disconnect(); + inline bool isConnected() const { return mem_ptr != nullptr; } inline int ShmValid() const { - return shared_data && shared_data->valid; + if ( shared_data && shared_data->valid ) { + timeval now = {}; + gettimeofday(&now, nullptr); + Debug(3, "Shared data is valid, checking heartbeat %" PRIi64 " - %" PRIi64 " = %" PRIi64" < %f", + static_cast(now.tv_sec), + static_cast(shared_data->zmc_heartbeat_time), + static_cast(now.tv_sec - shared_data->zmc_heartbeat_time), + config.watch_max_delay); + + if ((now.tv_sec - shared_data->zmc_heartbeat_time) < config.watch_max_delay) + return true; + } + return false; } - inline unsigned int Id() const { - return id; - } - inline const char *Name() const { - return name; - } + inline unsigned int Id() const { return id; } + inline const char *Name() const { return name.c_str(); } + inline unsigned int ServerId() { return server_id; } inline Storage *getStorage() { if ( ! storage ) { - storage = new Storage( storage_id ); + storage = new Storage(storage_id); } return storage; } - inline Function GetFunction() const { - return( function ); - } - inline bool Enabled() { + inline CameraType GetType() const { return type; } + inline Function GetFunction() const { return function; } + inline PacketQueue * GetPacketQueue() { return &packetqueue; } + inline bool Enabled() const { if ( function <= MONITOR ) return false; return enabled; } - inline const char *EventPrefix() const { - return event_prefix; + inline bool DecodingEnabled() const { + return decoding_enabled; } - inline bool Ready() { - if ( function <= MONITOR ) - return false; - return( image_count > ready_count ); + inline const char *EventPrefix() const { return event_prefix.c_str(); } + inline bool Ready() const { + if ( image_count >= ready_count ) { + return true; + } + Debug(2, "Not ready because image_count(%d) <= ready_count(%d)", image_count, ready_count); + return false; } - inline bool Active() { + inline bool Active() const { if ( function <= MONITOR ) return false; return( enabled && shared_data->active ); } - inline bool Exif() { - return embed_exif; - } + inline bool Exif() const { return embed_exif; } + inline bool RTSPServer() const { return rtsp_server; } + inline bool RecordAudio() { return record_audio; } + + /* + inline Purpose Purpose() { return purpose }; + inline Purpose Purpose( Purpose p ) { purpose = p; }; + */ + Orientation getOrientation() const; unsigned int Width() const { return width; } unsigned int Height() const { return height; } unsigned int Colours() const; unsigned int SubpixelOrder() const; - + + int GetAudioFrequency() const { return shared_data ? shared_data->audio_frequency : -1; } + int GetAudioChannels() const { return shared_data ? shared_data->audio_channels : -1; } + int GetOptSaveJPEGs() const { return savejpegs; } VideoWriter GetOptVideoWriter() const { return videowriter; } - const std::vector* GetOptEncoderParamsVec() const { return &encoderparamsvec; } - const std::string GetOptEncoderParams() const { return encoderparams; } + const std::string &GetEncoderOptions() const { return encoderparams; } + int OutputCodec() const { return output_codec; } + const std::string &Encoder() const { return encoder; } + const std::string &OutputContainer() const { return output_container; } + uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } - void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; } + void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; } + struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } - void SetVideoWriterStartTime(struct timeval &t) { video_store_data->recording = t; } + void SetVideoWriterStartTime(const struct timeval &t) { video_store_data->recording = t; } unsigned int GetPreEventCount() const { return pre_event_count; }; - struct timeval GetVideoBufferDuration() const { return video_buffer_duration; }; - int GetImageBufferCount() const { return image_buffer_count; }; - State GetState() const; - int GetImage( int index=-1, int scale=100 ); - Snapshot *getSnapshot() const; + int32_t GetImageBufferCount() const { return image_buffer_count; }; + State GetState() const { return (State)shared_data->state; } + + AVStream *GetAudioStream() const { return camera ? camera->getAudioStream() : nullptr; }; + AVCodecContext *GetAudioCodecContext() const { return camera ? camera->getAudioCodecContext() : nullptr; }; + AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; }; + AVCodecContext *GetVideoCodecContext() const { return camera ? camera->getVideoCodecContext() : nullptr; }; + + const std::string GetSecondPath() const { return second_path; }; + const std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; }; + const std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; }; + const std::string GetRTSPStreamName() const { return rtsp_streamname; }; + + int GetImage(int32_t index=-1, int scale=100); + ZMPacket *getSnapshot( int index=-1 ) const; struct timeval GetTimestamp( int index=-1 ) const; void UpdateAdaptiveSkip(); useconds_t GetAnalysisRate(); @@ -498,12 +544,18 @@ public: unsigned int GetLastWriteIndex() const; uint64_t GetLastEventId() const; double GetFPS() const; + void UpdateAnalysisFPS(); + void UpdateCaptureFPS(); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOff(); void CancelForced(); - TriggerState GetTriggerState() const { return (TriggerState)(trigger_data?trigger_data->trigger_state:TRIGGER_CANCEL); } + TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; } inline time_t getStartupTime() const { return shared_data->startup_time; } inline void setStartupTime( time_t p_time ) { shared_data->startup_time = p_time; } + inline void setHeartbeatTime( time_t p_time ) { shared_data->zmc_heartbeat_time = p_time; } + void get_ref_image(); + + int LabelSize() const { return label_size; } void actionReload(); void actionEnable(); @@ -516,20 +568,23 @@ public: int actionColour( int p_colour=-1 ); int actionContrast( int p_contrast=-1 ); - int PrimeCapture() const; + int PrimeCapture(); int PreCapture() const; int Capture(); int PostCapture() const; int Close(); + void CheckAction(); + unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. //unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet ); bool CheckSignal( const Image *image ); bool Analyse(); + bool Decode(); void DumpImage( Image *dump_image ) const; void TimestampImage( Image *ts_image, const struct timeval *ts_time ) const; - bool closeEvent(); + void closeEvent(); void Reload(); void ReloadZones(); @@ -540,17 +595,17 @@ public: std::vector Groups(); StringVector GroupNames(); - static int LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. + static std::vector> LoadMonitors(std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. #if ZM_HAS_V4L - static int LoadLocalMonitors(const char *device, Monitor **&monitors, Purpose purpose); + static std::vector> LoadLocalMonitors(const char *device, Purpose purpose); #endif // ZM_HAS_V4L - static int LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Monitor **&monitors, Purpose purpose); - static int LoadFileMonitors(const char *file, Monitor **&monitors, Purpose purpose); + static std::vector> LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Purpose purpose); + static std::vector> LoadFileMonitors(const char *file, Purpose purpose); #if HAVE_LIBAVFORMAT - static int LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose purpose); + static std::vector> LoadFfmpegMonitors(const char *file, Purpose purpose); #endif // HAVE_LIBAVFORMAT - static Monitor *Load(unsigned int id, bool load_zones, Purpose purpose); - static Monitor *Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); + static std::shared_ptr Load(unsigned int id, bool load_zones, Purpose purpose); + void Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose); //void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y ); //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 ); //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 ); @@ -558,9 +613,13 @@ public: #if HAVE_LIBAVCODEC //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 ); #endif // HAVE_LIBAVCODEC - double get_fps( ) const { - return fps; + double get_capture_fps( ) const { + return shared_data ? shared_data->capture_fps : 0.0; } + double get_analysis_fps( ) const { + return shared_data ? shared_data->analysis_fps : 0.0; + } + int Importance() { return importance; } }; #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 71a7a66d2..63f85d71b 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -17,23 +17,25 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" -#include "zm_db.h" -#include "zm_time.h" -#include "zm_mpeg.h" -#include "zm_signal.h" -#include "zm_monitor.h" #include "zm_monitorstream.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_time.h" #include #include +#include +#include +#include -const int MAX_SLEEP_USEC=1000000; // 1 sec +#ifdef __FreeBSD__ +#include +#endif bool MonitorStream::checkSwapPath(const char *path, bool create_path) { - struct stat stat_buf; if ( stat(path, &stat_buf) < 0 ) { - if ( create_path && errno == ENOENT ) { + if ( create_path and (errno == ENOENT) ) { Debug(3, "Swap path '%s' missing, creating", path); if ( mkdir(path, 0755) ) { Error("Can't mkdir %s: %s", path, strerror(errno)); @@ -73,7 +75,7 @@ bool MonitorStream::checkSwapPath(const char *path, bool create_path) { return false; } return true; -} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) +} // end bool MonitorStream::checkSwapPath(const char *path, bool create_path) void MonitorStream::processCommand(const CmdMsg *msg) { Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]); @@ -175,7 +177,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { case CMD_ZOOMIN : x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); + Debug(1, "Got ZOOM IN command, to %d,%d", x, y); switch ( zoom ) { case 100: zoom = 150; @@ -196,7 +198,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } break; case CMD_ZOOMOUT : - Debug( 1, "Got ZOOM OUT command" ); + Debug(1, "Got ZOOM OUT command"); switch ( zoom ) { case 500: zoom = 400; @@ -240,6 +242,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) { int id; int state; double fps; + double capture_fps; + double analysis_fps; int buffer_level; int rate; double delay; @@ -253,6 +257,8 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.id = monitor->Id(); if ( ! monitor->ShmValid() ) { status_data.fps = 0.0; + status_data.capture_fps = 0.0; + status_data.analysis_fps = 0.0; status_data.state = Monitor::UNKNOWN; //status_data.enabled = monitor->shared_data->active; status_data.enabled = false; @@ -260,10 +266,12 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.buffer_level = 0; } else { status_data.fps = monitor->GetFPS(); + status_data.capture_fps = monitor->get_capture_fps(); + status_data.analysis_fps = monitor->get_analysis_fps(); status_data.state = monitor->shared_data->state; //status_data.enabled = monitor->shared_data->active; - status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; - status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; + status_data.enabled = monitor->trigger_data->trigger_state != Monitor::TriggerState::TRIGGER_OFF; + status_data.forced = monitor->trigger_data->trigger_state == Monitor::TriggerState::TRIGGER_ON; if ( playback_buffer > 0 ) status_data.buffer_level = (MOD_ADD( (temp_write_index-temp_read_index), 0, temp_image_buffer_count )*100)/temp_image_buffer_count; else @@ -274,16 +282,19 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.rate = replay_rate; status_data.delay = TV_2_FLOAT(now) - TV_2_FLOAT(last_frame_timestamp); status_data.zoom = zoom; - Debug(2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", - status_data.buffer_level, - status_data.delayed, - status_data.paused, - status_data.rate, - status_data.delay, - status_data.zoom, - status_data.enabled, - status_data.forced - ); + Debug(2, "fps: %.2f capture_fps: %.2f analysis_fps: %.2f Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", + status_data.fps, + status_data.capture_fps, + status_data.analysis_fps, + status_data.buffer_level, + status_data.delayed, + status_data.paused, + status_data.rate, + status_data.delay, + status_data.zoom, + status_data.enabled, + status_data.forced + ); DataMsg status_msg; status_msg.msg_type = MSG_DATA_WATCH; @@ -292,14 +303,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) { if ( (nbytes = sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr))) < 0 ) { //if ( errno != EAGAIN ) { - Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); + Error("Can't sendto on sd %d: %s", sd, strerror(errno)); //exit( -1 ); } } Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); // quit after sending a status, if this was a quit request - if ( (MsgCommand)msg->msg_data[0]==CMD_QUIT ) { + if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) { zm_terminate = true; Debug(2, "Quitting"); return; @@ -307,19 +318,20 @@ void MonitorStream::processCommand(const CmdMsg *msg) { //Debug(2,"Updating framerate"); //updateFrameRate(monitor->GetFPS()); -} // end void MonitorStream::processCommand(const CmdMsg *msg) +} // end void MonitorStream::processCommand(const CmdMsg *msg) bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); - if ( type != STREAM_JPEG ) - send_raw = false; - if ( !config.timestamp_on_capture && timestamp ) + if ( + ( type != STREAM_JPEG ) + || + ( (!config.timestamp_on_capture) && timestamp ) + ) send_raw = false; if ( !send_raw ) { Image temp_image(filepath); - return sendFrame(&temp_image, timestamp); } else { int img_buffer_size = 0; @@ -453,33 +465,43 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { } // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) void MonitorStream::runStream() { - if ( type == STREAM_SINGLE ) { + if (type == STREAM_SINGLE) { // Not yet migrated over to stream class - SingleImage(scale); + if (checkInitialised()) + SingleImage(scale); + else + sendTextFrame("Unable to send image"); + return; } openComms(); - if ( type == STREAM_JPEG ) + if (type == STREAM_JPEG) fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); - if ( !checkInitialised() ) { - Error("Not initialized"); - while ( !(loadMonitor(monitor_id) || zm_terminate) ) { - sendTextFrame("Not connected"); - if ( connkey ) - checkCommandQueue(); + /* This is all about waiting and showing a useful message if the monitor isn't available */ + while (!zm_terminate) { + if (connkey) + checkCommandQueue(); + + if (!checkInitialised()) { + if (!loadMonitor(monitor_id)) { + if (!sendTextFrame("Not connected")) return; + } else { + if (!sendTextFrame("Unable to stream")) return; + } sleep(1); + } else { + break; } - if ( zm_terminate ) - return; } + if (zm_terminate) return; updateFrameRate(monitor->GetFPS()); // point to end which is theoretically not a valid value because all indexes are % image_buffer_count - unsigned int last_read_index = monitor->image_buffer_count; + int32_t last_read_index = monitor->image_buffer_count; time_t stream_start_time; time(&stream_start_time); @@ -503,7 +525,7 @@ void MonitorStream::runStream() { const int max_swap_len_suffix = 15; int swap_path_length = staticConfig.PATH_SWAP.length() + 1; // +1 for NULL terminator - int subfolder1_length = snprintf(nullptr, 0, "/zmswap-m%d", monitor->Id()) + 1; + int subfolder1_length = snprintf(nullptr, 0, "/zmswap-m%u", monitor->Id()) + 1; int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; @@ -540,15 +562,7 @@ void MonitorStream::runStream() { Debug(2, "Not using playback_buffer"); } // end if connkey & playback_buffer - // if MaxFPS < 0 as in 1/60 = 0.017 then we won't get another frame for 60 sec. - double capture_fps = monitor->GetFPS(); - double capture_max_fps = monitor->GetCaptureMaxFPS(); - if ( capture_max_fps && ( capture_fps > capture_max_fps ) ) { - Debug(1, "Using %.3f for fps instead of current fps %.3f", capture_max_fps, capture_fps); - capture_fps = capture_max_fps; - } - - while ( !zm_terminate ) { + while (!zm_terminate) { bool got_command = false; if ( feof(stdout) ) { Debug(2, "feof stdout"); @@ -556,7 +570,7 @@ void MonitorStream::runStream() { } else if ( ferror(stdout) ) { Debug(2, "ferror stdout"); break; - } else if ( !monitor->ShmValid() ) { + } else if (!monitor->ShmValid()) { Debug(2, "monitor not valid.... maybe we should wait until it comes back."); break; } @@ -679,34 +693,34 @@ void MonitorStream::runStream() { if ( last_read_index != monitor->shared_data->last_write_index ) { // have a new image to send - int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary + int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ( !paused && !delayed ) { last_read_index = monitor->shared_data->last_write_index; Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", index, frame_mod, frame_count, paused, delayed); // Send the next frame - Monitor::Snapshot *snap = &monitor->image_buffer[index]; + // + ZMPacket *snap = &monitor->image_buffer[index]; if ( !sendFrame(snap->image, snap->timestamp) ) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; + break; } + // Perhaps we should use NOW instead. + last_frame_timestamp = *(snap->timestamp); + //frame_sent = true; + // if ( frame_count == 0 ) { // Chrome will not display the first frame until it receives another. // Firefox is fine. So just send the first frame twice. if ( !sendFrame(snap->image, snap->timestamp) ) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; + break; } } - // Perhaps we should use NOW instead. - memcpy( - &last_frame_timestamp, - snap->timestamp, - sizeof(last_frame_timestamp) - ); - // frame_sent = true; temp_read_index = temp_write_index; } else { @@ -733,14 +747,14 @@ void MonitorStream::runStream() { } else { Debug(2, "Would have sent keepalive frame, but had no paused_image"); } - } // end if actual_delta_time > 5 - } // end if change in zoom - } // end if paused or not - } // end if should send frame + } // end if actual_delta_time > 5 + } // end if change in zoom + } // end if paused or not + } // end if should send frame if ( buffered_playback && !paused ) { if ( monitor->shared_data->valid ) { - if ( monitor->image_buffer[index].timestamp->tv_sec ) { + if ( monitor->shared_timestamps[index].tv_sec ) { int temp_index = temp_write_index%temp_image_buffer_count; Debug(2, "Storing frame %d", temp_index); if ( !temp_image_buffer[temp_index].valid ) { @@ -752,8 +766,11 @@ void MonitorStream::runStream() { temp_index); temp_image_buffer[temp_index].valid = true; } - memcpy(&(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp)); - monitor->image_buffer[index].image->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality); + temp_image_buffer[temp_index].timestamp = monitor->shared_timestamps[index]; + monitor->image_buffer[index].image->WriteJpeg( + temp_image_buffer[temp_index].file_name, + config.jpeg_file_quality + ); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count); if ( temp_write_index == temp_read_index ) { // Go back to live viewing @@ -775,17 +792,20 @@ void MonitorStream::runStream() { } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); - if ( sleep_time > MAX_SLEEP_USEC ) { + if ( sleep_time > MonitorStream::MAX_SLEEP_USEC ) { // Shouldn't sleep for long because we need to check command queue, etc. - sleep_time = MAX_SLEEP_USEC; - Debug(3, "Sleeping for MAX_SLEEP_USEC %dus", sleep_time); + sleep_time = MonitorStream::MAX_SLEEP_USEC; + Debug(3, "Sleeping for MAX_SLEEP_USEC %luus", sleep_time); } else { - Debug(3, "Sleeping for %dus", sleep_time); + Debug(3, "Sleeping for %luus", sleep_time); } usleep(sleep_time); if ( ttl ) { if ( (now.tv_sec - stream_start_time) > ttl ) { - Debug(2, "now(%d) - start(%d) > ttl(%d) break", now.tv_sec, stream_start_time, ttl); + Debug(2, "now(%" PRIi64 ") - start(%" PRIi64 " ) > ttl(%" PRIi64 ") break", + static_cast(now.tv_sec), + static_cast(stream_start_time), + static_cast(ttl)); break; } } @@ -839,7 +859,13 @@ void MonitorStream::SingleImage(int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); + while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { + Debug(1, "Waiting for capture to begin"); + usleep(100000); + } + int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; + Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index); + ZMPacket *snap = &(monitor->image_buffer[index]); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { @@ -857,11 +883,11 @@ void MonitorStream::SingleImage(int scale) { "Content-Type: image/jpeg\r\n\r\n", img_buffer_size); fwrite(img_buffer, img_buffer_size, 1, stdout); -} +} // end void MonitorStream::SingleImage(int scale) void MonitorStream::SingleImageRaw(int scale) { Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); + ZMPacket *snap = monitor->getSnapshot(); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { @@ -874,11 +900,11 @@ void MonitorStream::SingleImageRaw(int scale) { } fprintf(stdout, - "Content-Length: %d\r\n" + "Content-Length: %u\r\n" "Content-Type: image/x-rgb\r\n\r\n", snap_image->Size()); fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout); -} +} // end void MonitorStream::SingleImageRaw(int scale) #ifdef HAVE_ZLIB_H void MonitorStream::SingleImageZip(int scale) { @@ -886,7 +912,7 @@ void MonitorStream::SingleImageZip(int scale) { static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - Monitor::Snapshot *snap = monitor->getSnapshot(); + ZMPacket *snap = monitor->getSnapshot(); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { @@ -900,9 +926,9 @@ void MonitorStream::SingleImageZip(int scale) { snap_image->Zip(img_buffer, &img_buffer_size); fprintf(stdout, - "Content-Length: %ld\r\n" + "Content-Length: %lu\r\n" "Content-Type: image/x-rgbz\r\n\r\n", img_buffer_size); fwrite(img_buffer, img_buffer_size, 1, stdout); -} +} // end void MonitorStream::SingleImageZip(int scale) #endif // HAVE_ZLIB_H diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h index 24655b636..0c1f92777 100644 --- a/src/zm_monitorstream.h +++ b/src/zm_monitorstream.h @@ -20,11 +20,8 @@ #ifndef ZM_MONITORSTREAM_H #define ZM_MONITORSTREAM_H -#include "zm.h" -#include "zm_coord.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_monitor.h" +#include "zm_stream.h" +#include class MonitorStream : public StreamBase { protected: @@ -50,7 +47,7 @@ class MonitorStream : public StreamBase { bool checkSwapPath(const char *path, bool create_path); bool sendFrame(const char *filepath, struct timeval *timestamp); bool sendFrame(Image *image, struct timeval *timestamp); - void processCommand(const CmdMsg *msg); + void processCommand(const CmdMsg *msg) override; void SingleImage(int scale=100); void SingleImageRaw(int scale=100); #ifdef HAVE_ZLIB_H @@ -59,8 +56,14 @@ class MonitorStream : public StreamBase { public: MonitorStream() : - temp_image_buffer(nullptr), temp_image_buffer_count(0), temp_read_index(0), temp_write_index(0), - ttl(0), playback_buffer(0), delayed(false), frame_count(0) { + temp_image_buffer(nullptr), + temp_image_buffer_count(0), + temp_read_index(0), + temp_write_index(0), + ttl(0), + playback_buffer(0), + delayed(false), + frame_count(0) { } void setStreamBuffer(int p_playback_buffer) { playback_buffer = p_playback_buffer; @@ -71,7 +74,7 @@ class MonitorStream : public StreamBase { bool setStreamStart(int monitor_id) { return loadMonitor(monitor_id); } - void runStream(); + void runStream() override; }; #endif // ZM_MONITORSTREAM_H diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 225cdaa5e..5ab1ad5dc 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -17,13 +17,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include - -#include "zm.h" -#include "zm_rgb.h" #include "zm_mpeg.h" +#include "zm_logger.h" +#include "zm_rgb.h" +#include +#include + #if HAVE_LIBAVCODEC extern "C" { #include @@ -101,7 +101,7 @@ void VideoStream::SetupFormat( ) { ofc = s; #endif if ( !ofc ) { - Fatal("avformat_alloc_..._context failed: %d", ofc); + Fatal("avformat_alloc_..._context failed"); } of = ofc->oformat; @@ -264,22 +264,21 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei void VideoStream::SetParameters( ) { } -const char *VideoStream::MimeType( ) const { +const char *VideoStream::MimeType() const { for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) { - if ( strcmp( format, mime_data[i].format ) == 0 ) { - Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); + if ( strcmp(format, mime_data[i].format) == 0 ) { + Debug(1, "MimeType is \"%s\"", mime_data[i].mime_type); return mime_data[i].mime_type; } } const char *mime_type = of->mime_type; if ( !mime_type ) { - std::string mime = "video/"; - mime = mime.append( format ); - mime_type = mime.c_str( ); - Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); + std::string mime = std::string("video/") + format; + mime_type = strdup(mime.c_str()); // mem leak + Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type); } - Debug(1, "MimeType is \"%s\"", mime_type ); + Debug(1, "MimeType is \"%s\"", mime_type); return mime_type; } diff --git a/src/zm_mpeg.h b/src/zm_mpeg.h index 6e75f8743..4999f1328 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -21,6 +21,7 @@ #define ZM_MPEG_H #include "zm_ffmpeg.h" +#include #if HAVE_LIBAVCODEC diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 36407ba21..7aef2ddca 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -16,35 +16,266 @@ //You should have received a copy of the GNU General Public License //along with ZoneMinder. If not, see . - #include "zm_packet.h" -#include "zm_ffmpeg.h" +#include "zm_ffmpeg.h" +#include "zm_image.h" +#include "zm_logger.h" #include using namespace std; +AVPixelFormat target_format = AV_PIX_FMT_NONE; -ZMPacket::ZMPacket( AVPacket *p ) { - frame = nullptr; - image = nullptr; - av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { - Error("error refing packet"); - } - gettimeofday( ×tamp, nullptr ); +ZMPacket::ZMPacket() : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + timestamp(nullptr), + buffer(nullptr), + image(nullptr), + analysis_image(nullptr), + score(-1), + codec_type(AVMEDIA_TYPE_UNKNOWN), + image_index(-1), + codec_imgsize(0), + pts(0), + decoded(0) +{ + av_init_packet(&packet); + packet.size = 0; // So we can detect whether it has been filled. } -ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { - frame = nullptr; - image = nullptr; - av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { +ZMPacket::ZMPacket(ZMPacket &p) : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + timestamp(nullptr), + buffer(nullptr), + image(nullptr), + analysis_image(nullptr), + score(-1), + codec_type(AVMEDIA_TYPE_UNKNOWN), + image_index(-1), + codec_imgsize(0), + pts(0), + decoded(0) +{ + av_init_packet(&packet); + packet.size = 0; + packet.data = nullptr; + if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) { Error("error refing packet"); - } - timestamp = *t; + } + timestamp = new struct timeval; + *timestamp = *p.timestamp; } ZMPacket::~ZMPacket() { - zm_av_packet_unref( &packet ); + zm_av_packet_unref(&packet); + if ( in_frame ) av_frame_free(&in_frame); + if ( out_frame ) av_frame_free(&out_frame); + if ( buffer ) av_freep(&buffer); + if ( analysis_image ) delete analysis_image; + if ( image ) delete image; + if ( timestamp ) delete timestamp; } +/* returns < 0 on error, 0 on not ready, int bytes consumed on success + * This functions job is to populate in_frame with the image in an appropriate + * format. It MAY also populate image if able to. In this case in_frame is populated + * by the image buffer. + */ +int ZMPacket::decode(AVCodecContext *ctx) { + Debug(4, "about to decode video, image_index is (%d)", image_index); + + if ( in_frame ) { + Error("Already have a frame?"); + } else { + in_frame = zm_av_frame_alloc(); + } + + // packets are always stored in AV_TIME_BASE_Q so need to convert to codec time base + //av_packet_rescale_ts(&packet, AV_TIME_BASE_Q, ctx->time_base); + + int ret = zm_send_packet_receive_frame(ctx, in_frame, packet); + if ( ret < 0 ) { + if ( AVERROR(EAGAIN) != ret ) { + Warning("Unable to receive frame : code %d %s.", + ret, av_make_error_string(ret).c_str()); + } + av_frame_free(&in_frame); + return 0; + } + int bytes_consumed = ret; + if ( ret > 0 ) { + zm_dump_video_frame(in_frame, "got frame"); + +#if HAVE_LIBAVUTIL_HWCONTEXT_H +#if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) + + if ( fix_deprecated_pix_fmt(ctx->sw_pix_fmt) != fix_deprecated_pix_fmt(static_cast(in_frame->format)) ) { + Debug(1, "Have different format ctx->pix_fmt %s ?= ctx->sw_pix_fmt %s in_frame->format %s.", + av_get_pix_fmt_name(ctx->pix_fmt), + av_get_pix_fmt_name(ctx->sw_pix_fmt), + av_get_pix_fmt_name(static_cast(in_frame->format)) + ); +#if 0 + if ( target_format == AV_PIX_FMT_NONE and ctx->hw_frames_ctx and (image->Colours() == 4) ) { + // Look for rgb0 in list of supported formats + enum AVPixelFormat *formats; + if ( 0 <= av_hwframe_transfer_get_formats( + ctx->hw_frames_ctx, + AV_HWFRAME_TRANSFER_DIRECTION_FROM, + &formats, + 0 + ) ) { + for (int i = 0; formats[i] != AV_PIX_FMT_NONE; i++) { + Debug(1, "Available dest formats %d %s", + formats[i], + av_get_pix_fmt_name(formats[i]) + ); + if ( formats[i] == AV_PIX_FMT_RGB0 ) { + target_format = formats[i]; + break; + } // endif RGB0 + } // end foreach support format + av_freep(&formats); + } // endif success getting list of formats + } // end if target_format not set +#endif + + AVFrame *new_frame = zm_av_frame_alloc(); +#if 0 + if ( target_format == AV_PIX_FMT_RGB0 ) { + if ( image ) { + if ( 0 > image->PopulateFrame(new_frame) ) { + delete new_frame; + new_frame = zm_av_frame_alloc(); + delete image; + image = nullptr; + new_frame->format = target_format; + } + } + } +#endif + /* retrieve data from GPU to CPU */ + zm_dump_video_frame(in_frame, "Before hwtransfer"); + ret = av_hwframe_transfer_data(new_frame, in_frame, 0); + if ( ret < 0 ) { + Error("Unable to transfer frame: %s, continuing", + av_make_error_string(ret).c_str()); + av_frame_free(&in_frame); + av_frame_free(&new_frame); + return 0; + } + ret = av_frame_copy_props(new_frame, in_frame); + if ( ret < 0 ) { + Error("Unable to copy props: %s, continuing", + av_make_error_string(ret).c_str()); + } + + zm_dump_video_frame(new_frame, "After hwtransfer"); +#if 0 + if ( new_frame->format == AV_PIX_FMT_RGB0 ) { + new_frame->format = AV_PIX_FMT_RGBA; + zm_dump_video_frame(new_frame, "After hwtransfer setting to rgba"); + } +#endif + av_frame_free(&in_frame); + in_frame = new_frame; + } else +#endif +#endif + Debug(2, "Same pix format %s so not hwtransferring. sw_pix_fmt is %s", + av_get_pix_fmt_name(ctx->pix_fmt), + av_get_pix_fmt_name(ctx->sw_pix_fmt) + ); +#if 0 + if ( image ) { + image->Assign(in_frame); + } +#endif + } // end if if ( ret > 0 ) { + return bytes_consumed; +} // end ZMPacket::decode + +Image *ZMPacket::get_image(Image *i) { + if ( !in_frame ) { + Error("Can't get image without frame.. maybe need to decode first"); + return nullptr; + } + if ( !image ) { + if ( !i ) { + Error("Need a pre-allocated image buffer"); + return nullptr; + } + image = i; + } + image->Assign(in_frame); + return image; +} + +Image *ZMPacket::set_image(Image *i) { + image = i; + return image; +} + +AVPacket *ZMPacket::set_packet(AVPacket *p) { + if ( zm_av_packet_ref(&packet, p) < 0 ) { + Error("error refing packet"); + } + //ZM_DUMP_PACKET(packet, "zmpacket:"); + gettimeofday(timestamp, nullptr); + keyframe = p->flags & AV_PKT_FLAG_KEY; + return &packet; +} + +AVFrame *ZMPacket::get_out_frame(int width, int height, AVPixelFormat format) { + if (!out_frame) { + out_frame = zm_av_frame_alloc(); + if (!out_frame) { + Error("Unable to allocate a frame"); + return nullptr; + } + +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + + codec_imgsize = av_image_get_buffer_size( + format, width, height, 32); + Debug(1, "buffer size %u from %s %dx%d", codec_imgsize, av_get_pix_fmt_name(format), width, height); + buffer = (uint8_t *)av_malloc(codec_imgsize); + int ret; + if ((ret=av_image_fill_arrays( + out_frame->data, + out_frame->linesize, + buffer, + format, + width, + height, + 32))<0) { + Error("Failed to fill_arrays %s", av_make_error_string(ret).c_str()); + av_frame_free(&out_frame); + return nullptr; + } +#else + codec_imgsize = avpicture_get_size( + format, + width, + >height); + buffer = (uint8_t *)av_malloc(codec_imgsize); + avpicture_fill( + (AVPicture *)out_frame, + buffer, + format, + width, + height + ); +#endif + out_frame->width = width; + out_frame->height = height; + out_frame->format = format; + } + return out_frame; +} // end AVFrame *ZMPacket::get_out_frame( AVCodecContext *ctx ); diff --git a/src/zm_packet.h b/src/zm_packet.h index bdb67cb57..a7deca325 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -20,6 +20,13 @@ #ifndef ZM_PACKET_H #define ZM_PACKET_H +#include "zm_logger.h" +#include "zm_zone.h" + +#include +#include +#include + extern "C" { #include } @@ -27,20 +34,90 @@ extern "C" { #ifdef __FreeBSD__ #include #endif // __FreeBSD__ -#include "zm_image.h" + +class Image; class ZMPacket { public: - AVPacket packet; // Input packet, undecoded - AVFrame *frame; // Input image, decoded - Image *image; // Our internal image oject representing this frame - struct timeval timestamp; + std::mutex mutex_; + std::condition_variable condition_; + + int keyframe; + AVStream *stream; // Input stream + AVPacket packet; // Input packet, undecoded + AVFrame *in_frame; // Input image, decoded Theoretically only filled if needed. + AVFrame *out_frame; // output image, Only filled if needed. + struct timeval *timestamp; + uint8_t *buffer; // buffer used in image + Image *image; + Image *analysis_image; + int score; + AVMediaType codec_type; + int image_index; + int codec_imgsize; + int64_t pts; // pts in the packet can be in another time base. This MUST be in AV_TIME_BASE_Q + bool decoded; + std::vector zone_stats; + public: AVPacket *av_packet() { return &packet; } - ZMPacket( AVPacket *packet, struct timeval *timestamp ); - explicit ZMPacket( AVPacket *packet ); + AVPacket *set_packet(AVPacket *p) ; + AVFrame *av_frame() { return out_frame; } + Image *get_image(Image *i=nullptr); + Image *set_image(Image *); + + int is_keyframe() { return keyframe; }; + int decode( AVCodecContext *ctx ); + explicit ZMPacket(Image *image); + explicit ZMPacket(ZMPacket &packet); + ZMPacket(); ~ZMPacket(); + + //AVFrame *get_out_frame(const AVCodecContext *ctx); + AVFrame *get_out_frame(int width, int height, AVPixelFormat format); + int get_codec_imgsize() { return codec_imgsize; }; +}; + +class ZMLockedPacket { + public: + ZMPacket *packet_; + std::unique_lock lck_; + bool locked; + + explicit ZMLockedPacket(ZMPacket *p) : + packet_(p), + lck_(packet_->mutex_, std::defer_lock), + locked(false) { + } + ~ZMLockedPacket() { + if (locked) unlock(); + } + + void lock() { + Debug(4, "locking packet %d", packet_->image_index); + lck_.lock(); + locked = true; + Debug(4, "packet %d locked", packet_->image_index); + }; + + bool trylock() { + Debug(4, "TryLocking packet %d", packet_->image_index); + locked = lck_.try_lock(); + return locked; + }; + + void unlock() { + Debug(4, "packet %d unlocked", packet_->image_index); + locked = false; + lck_.unlock(); + packet_->condition_.notify_all(); + }; + + void wait() { + Debug(4, "packet %d waiting", packet_->image_index); + packet_->condition_.wait(lck_); + } }; #endif /* ZM_PACKET_H */ diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 7dbe31bd7..a1f149bb4 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -17,223 +17,482 @@ //along with ZoneMinder. If not, see . +// PacketQueue must know about all iterators and manage them + #include "zm_packetqueue.h" + #include "zm_ffmpeg.h" +#include "zm_packet.h" +#include "zm_signal.h" #include -#include "zm_time.h" -zm_packetqueue::zm_packetqueue( int p_max_stream_id ) { - max_stream_id = p_max_stream_id; +PacketQueue::PacketQueue(): + video_stream_id(-1), + max_video_packet_count(-1), + pre_event_video_packet_count(-1), + max_stream_id(-1), + packet_counts(nullptr), + deleting(false), + keep_keyframes(false) +{ +} + +/* Assumes queue is empty when adding streams + * Assumes first stream added will be the video stream + */ +int PacketQueue::addStream() { + deleting = false; + if (max_stream_id == -1) { + video_stream_id = 0; + max_stream_id = 0; + } else { + max_stream_id ++; + } + + if (packet_counts) delete[] packet_counts; packet_counts = new int[max_stream_id+1]; - for ( int i=0; i <= max_stream_id; ++i ) + for (int i=0; i <= max_stream_id; ++i) packet_counts[i] = 0; + return max_stream_id; } -zm_packetqueue::~zm_packetqueue() { - clearQueue(); - delete[] packet_counts; - packet_counts = nullptr; +PacketQueue::~PacketQueue() { + clear(); + if (packet_counts) { + delete[] packet_counts; + packet_counts = nullptr; + } + while (!iterators.empty()) { + packetqueue_iterator *it = iterators.front(); + iterators.pop_front(); + delete it; + } + Debug(4, "Done in destructor"); } -bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) { +/* Enqueues the given packet. Will maintain the it pointer and image packet counts. + * If we have reached our max image packet count, it will pop off as many packets as are needed. + * Thus it will ensure that the same packet never gets queued twice. + */ - if ( - ( zm_packet->packet.dts == AV_NOPTS_VALUE ) - || - ( packet_counts[zm_packet->packet.stream_index] <= 0 ) - ) { - Debug(2,"Inserting packet with dts %" PRId64 " because queue %d is empty (queue size: %d) or invalid dts", - zm_packet->packet.dts, zm_packet->packet.stream_index, packet_counts[zm_packet->packet.stream_index] +bool PacketQueue::queuePacket(ZMPacket* add_packet) { + Debug(4, "packetqueue queuepacket %p %d", add_packet, add_packet->image_index); + if (iterators.empty()) { + Debug(4, "No iterators so no one needs us to queue packets."); + return false; + } + if (!packet_counts[video_stream_id] and !add_packet->keyframe) { + Debug(4, "No video keyframe so no one needs us to queue packets."); + return false; + } + { + std::unique_lock lck(mutex); + + if (add_packet->packet.stream_index == video_stream_id) { + if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) { + Warning("You have set the max video packets in the queue to %u." + " The queue is full. Either Analysis is not keeping up or" + " your camera's keyframe interval is larger than this setting." + " We are dropping packets.", max_video_packet_count); + if (add_packet->keyframe) { + // Have a new keyframe, so delete everything + while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) { + ZMPacket *zm_packet = *pktQueue.begin(); + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) { + Debug(1, "Found locked packet when trying to free up video packets. Can't continue"); + delete lp; + break; + } + delete lp; + + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ( *(*iterator_it) == zm_packet ) { + Debug(1, "Bumping IT because it is at the front that we are deleting"); + ++(*iterators_it); + } + } // end foreach iterator + + pktQueue.pop_front(); + packet_counts[zm_packet->packet.stream_index] -= 1; + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + zm_packet->packet.stream_index, + zm_packet->image_index, + zm_packet->keyframe, + packet_counts[video_stream_id], + max_video_packet_count, + pktQueue.size()); + delete zm_packet; + } // end while + } + } // end if too many video packets + if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) { + Error("Unable to free up older packets. Not queueing this video packet."); + return false; + } + } // end if this packet is a video packet + + pktQueue.push_back(add_packet); + packet_counts[add_packet->packet.stream_index] += 1; + Debug(2, "packet counts for %d is %d", + add_packet->packet.stream_index, + packet_counts[add_packet->packet.stream_index]); + + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + if (*iterator_it == pktQueue.end()) { + Debug(4, "pointing it %p to back", iterator_it); + --(*iterator_it); + } else { + Debug(4, "it %p not at end", iterator_it); + } + } // end foreach iterator + } // end lock scope + // We signal on every packet because someday we may analyze sound + Debug(4, "packetqueue queuepacket, unlocked signalling"); + condition.notify_all(); + + return true; +} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) + +void PacketQueue::clearPackets(ZMPacket *add_packet) { + // Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one. + // No good. Have to satisfy two conditions: + // 1. packetqueue starts with a video keyframe + // 2. Have minimum # of video packets + // 3. No packets can be locked + // 4. No iterator can point to one of the packets + // + // So start at the beginning, counting video packets until the next keyframe. + // Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them. + if (keep_keyframes and ! ( + add_packet->packet.stream_index == video_stream_id + and + add_packet->keyframe + and + (packet_counts[video_stream_id] > pre_event_video_packet_count) + and + *(pktQueue.begin()) != add_packet + ) + ) { + Debug(3, "stream index %d ?= video_stream_id %d, keyframe %d, keep_keyframes %d, counts %d > pre_event_count %d at begin %d", + add_packet->packet.stream_index, video_stream_id, add_packet->keyframe, keep_keyframes, packet_counts[video_stream_id], pre_event_video_packet_count, + ( *(pktQueue.begin()) != add_packet ) ); - // No dts value, can't so much with it - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; + return; } + std::unique_lock lck(mutex); + if (!pktQueue.size()) return; -#if 0 - std::list::reverse_iterator it = pktQueue.rbegin(); + // If analysis_it isn't at the end, we need to keep that many additional packets + int tail_count = 0; + if (pktQueue.back() != add_packet) { + packetqueue_iterator it = pktQueue.end(); + --it; + while (*it != add_packet) { + if ((*it)->packet.stream_index == video_stream_id) + ++tail_count; + --it; + } + } + Debug(1, "Tail count is %d, queue size is %lu", tail_count, pktQueue.size()); - // Scan through the queue looking for a packet for our stream with a dts <= ours. - while ( it != pktQueue.rend() ) { - AVPacket *av_packet = &((*it)->packet); + if (!keep_keyframes) { + // If not doing passthrough, we don't care about starting with a keyframe so logic is simpler + while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > pre_event_video_packet_count + tail_count)) { + ZMPacket *zm_packet = *pktQueue.begin(); + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) break; + delete lp; - Debug(2, "Looking at packet with stream index (%d) with dts %" PRId64, - av_packet->stream_index, av_packet->dts); - if ( av_packet->stream_index == zm_packet->packet.stream_index ) { - if ( - ( av_packet->dts != AV_NOPTS_VALUE ) - && - ( av_packet->dts <= zm_packet->packet.dts) - ) { - Debug(2, "break packet with stream index (%d) with dts %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts); + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { + Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); break; } - } else { // Not same stream, compare timestamps - if ( tvDiffUsec(((*it)->timestamp, zm_packet->timestamp) ) <= 0 ) { - Debug(2, "break packet with stream index (%d) with dts %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts); + + pktQueue.pop_front(); + packet_counts[zm_packet->packet.stream_index] -= 1; + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + zm_packet->packet.stream_index, + zm_packet->image_index, + zm_packet->keyframe, + packet_counts[video_stream_id], + pre_event_video_packet_count, + pktQueue.size()); + delete zm_packet; + } // end while + return; + } + + packetqueue_iterator it = pktQueue.begin(); + packetqueue_iterator next_front = pktQueue.begin(); + int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking + + // First packet is special because we know it is a video keyframe and only need to check for lock + ZMPacket *zm_packet = *it; + Debug(1, "trying lock on first packet"); + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (lp->trylock()) { + Debug(1, "Have lock on first packet"); + ++it; + delete lp; + + if (it == pktQueue.end()) { + Debug(1, "Hit end already"); + it = pktQueue.begin(); + } else { + // Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that + while (*it != add_packet) { + zm_packet = *it; + lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) { + delete lp; break; } + delete lp; + + if (is_there_an_iterator_pointing_to_packet(zm_packet) and (pktQueue.begin() == next_front)) { + Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); + break; + } + + if (zm_packet->packet.stream_index == video_stream_id) { + if (zm_packet->keyframe) { + Debug(3, "Have a video keyframe so setting next front to it"); + next_front = it; + } + ++video_packets_to_delete; + Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d", + video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count); + if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) { + break; + } + } + it++; + } // end while } - it++; - } // end while not the end of the queue + } // end if first packet not locked + Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d", + ( *it == add_packet ), + ( next_front == pktQueue.begin() ) + ); + if ( next_front != pktQueue.begin() ) { + while ( pktQueue.begin() != next_front ) { + ZMPacket *zm_packet = *pktQueue.begin(); + if ( !zm_packet ) { + Error("NULL zm_packet in queue"); + continue; + } - if ( it != pktQueue.rend() ) { - Debug(2, "Found packet with stream index (%d) with dts %" PRId64 " <= %" PRId64, - (*it)->packet.stream_index, (*it)->packet.dts, zm_packet->packet.dts); - if ( it == pktQueue.rbegin() ) { - Debug(2,"Inserting packet with dts %" PRId64 " at end", zm_packet->packet.dts); - // No dts value, can't so much with it - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + zm_packet->packet.stream_index, + zm_packet->image_index, + zm_packet->keyframe, + packet_counts[video_stream_id], + pre_event_video_packet_count, + pktQueue.size()); + pktQueue.pop_front(); + packet_counts[zm_packet->packet.stream_index] -= 1; + delete zm_packet; } - // Convert to a forward iterator so that we can insert at end - std::list::iterator f_it = it.base(); + } // end if have at least max_video_packet_count video packets remaining + // We signal on every packet because someday we may analyze sound - Debug(2, "Insert packet before packet with stream index (%d) with dts %" PRId64 " for dts %" PRId64, - (*f_it)->packet.stream_index, (*f_it)->packet.dts, zm_packet->packet.dts); + return; +} // end voidPacketQueue::clearPackets(ZMPacket* zm_packet) - pktQueue.insert(f_it, zm_packet); - - packet_counts[zm_packet->packet.stream_index] += 1; - return true; - } - Debug(1,"Unable to insert packet for stream %d with dts %" PRId64 " into queue.", - zm_packet->packet.stream_index, zm_packet->packet.dts); -#endif - pktQueue.push_back(zm_packet); - packet_counts[zm_packet->packet.stream_index] += 1; - return true; -} // end bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) - -bool zm_packetqueue::queuePacket(AVPacket* av_packet) { - ZMPacket *zm_packet = new ZMPacket(av_packet); - return queuePacket(zm_packet); -} - -ZMPacket* zm_packetqueue::popPacket( ) { +ZMLockedPacket* PacketQueue::popPacket( ) { + Debug(4, "pktQueue size %zu", pktQueue.size()); if ( pktQueue.empty() ) { return nullptr; } + Debug(4, "poPacket Mutex locking"); + std::unique_lock lck(mutex); + + ZMPacket *zm_packet = pktQueue.front(); + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ( *(*iterator_it) == zm_packet ) { + Debug(4, "Bumping it because it is at the front that we are deleting"); + ++(*iterators_it); + } + } // end foreach iterator + + ZMLockedPacket *lp = new ZMLockedPacket (zm_packet); + lp->lock(); - ZMPacket *packet = pktQueue.front(); pktQueue.pop_front(); - packet_counts[packet->packet.stream_index] -= 1; + packet_counts[zm_packet->packet.stream_index] -= 1; - return packet; -} + return lp; +} // popPacket -unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) { - - Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size()); - frames_to_keep += 1; + +/* Keeps frames_to_keep frames of the provided stream, which theoretically is the video stream + * Basically it starts at the end, moving backwards until it finds the minimum video frame. + * Then it should probably move forward to find a keyframe. The first video frame must always be a keyframe. + * So really frames_to_keep is a maximum which isn't so awesome.. maybe we should go back farther to find the keyframe in which case + * frames_to_keep in a minimum + */ + +unsigned int PacketQueue::clear(unsigned int frames_to_keep, int stream_id) { + Debug(3, "Clearing all but %d frames, queue has %zu", frames_to_keep, pktQueue.size()); if ( pktQueue.empty() ) { - Debug(3, "Queue is empty"); return 0; } - std::list::reverse_iterator it; - ZMPacket *packet = nullptr; + // If size is <= frames_to_keep since it could contain audio, we can't possibly do anything + if ( pktQueue.size() <= frames_to_keep ) { + return 0; + } + Debug(5, "Locking in clear"); + std::unique_lock lck(mutex); - for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { - ZMPacket *zm_packet = *it; + packetqueue_iterator it = pktQueue.end()--; // point to last element instead of end + ZMPacket *zm_packet = nullptr; + + while ( (it != pktQueue.begin()) and frames_to_keep ) { + zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + Debug(3, "Looking at packet with stream index (%d) with keyframe(%d), Image_index(%d) frames_to_keep is (%d)", + av_packet->stream_index, zm_packet->keyframe, zm_packet->image_index, frames_to_keep ); // Want frames_to_keep video keyframes. Otherwise, we may not have enough - if ( ( av_packet->stream_index == stream_id) ) { - //&& ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { + if ( av_packet->stream_index == stream_id ) { frames_to_keep --; } + it --; } - // Make sure we start on a keyframe - for ( ; it != pktQueue.rend(); ++it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - - Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep); - - // Want frames_to_keep video keyframes. Otherwise, we may not have enough - if ( (av_packet->stream_index == stream_id) && (av_packet->flags & AV_PKT_FLAG_KEY) ) { - Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", - av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep); - break; + // Either at beginning or frames_to_keep == 0 + + if ( it == pktQueue.begin() ) { + if ( frames_to_keep ) { + Warning("Couldn't remove any packets, needed %d", frames_to_keep); } + mutex.unlock(); + return 0; } - if ( frames_to_keep ) { - Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep); - } - if ( it != pktQueue.rend() ) { - // We want to keep this packet, so advance to the next - ++it; - } - unsigned int delete_count = 0; - while ( it != pktQueue.rend() ) { - Debug(4, "Deleting a packet from the front, count is (%d)", delete_count); - packet = pktQueue.front(); - pktQueue.pop_front(); - packet_counts[packet->packet.stream_index] -= 1; - delete packet; - - delete_count += 1; - } - packet = nullptr; // tidy up for valgrind - Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size()); - return delete_count; -} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) - -void zm_packetqueue::clearQueue() { - ZMPacket *packet = nullptr; int delete_count = 0; - while ( !pktQueue.empty() ) { - packet = pktQueue.front(); - packet_counts[packet->packet.stream_index] -= 1; + + // Else not at beginning, are pointing at packet before the last video packet + while ( pktQueue.begin() != it ) { + Debug(4, "Deleting a packet from the front, count is (%d), queue size is %zu", + delete_count, pktQueue.size()); + zm_packet = pktQueue.front(); + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ( *(*iterator_it) == zm_packet ) { + Debug(4, "Bumping it because it is at the front that we are deleting"); + ++(*iterators_it); + } + } // end foreach iterator + packet_counts[zm_packet->packet.stream_index] --; pktQueue.pop_front(); - delete packet; + //if ( zm_packet->image_index == -1 ) + delete zm_packet; + delete_count += 1; - } - Debug(3, "Deleted (%d) packets", delete_count ); + } // while our iterator is not the first packet + Debug(3, "Deleted %d packets, %zu remaining", delete_count, pktQueue.size()); + return delete_count; +} // end unsigned int PacketQueue::clear( unsigned int frames_to_keep, int stream_id ) + +void PacketQueue::clear() { + deleting = true; + condition.notify_all(); + Debug(1, "Clearing packetqueue"); + std::unique_lock lck(mutex); + + while (!pktQueue.empty()) { + ZMPacket *packet = pktQueue.front(); + // Someone might have this packet, but not for very long and since we have locked the queue they won't be able to get another one + ZMLockedPacket *lp = new ZMLockedPacket(packet); + lp->lock(); + pktQueue.pop_front(); + delete lp; + delete packet; + } + + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + *iterator_it = pktQueue.begin(); + } // end foreach iterator + + if ( packet_counts ) delete[] packet_counts; + packet_counts = nullptr; + max_stream_id = -1; + + condition.notify_all(); } // clear queue keeping only specified duration of video -- return number of pkts removed -unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) { +unsigned int PacketQueue::clear(struct timeval *duration, int streamId) { - if (pktQueue.empty()) { + if ( pktQueue.empty() ) { return 0; } - struct timeval keep_from; - std::list::reverse_iterator it; - it = pktQueue.rbegin(); + Debug(4, "Locking in clear"); + std::unique_lock lck(mutex); - timersub(&(*it)->timestamp, duration, &keep_from); + struct timeval keep_from; + std::list::reverse_iterator it = pktQueue.rbegin(); + + struct timeval *t = (*it)->timestamp; + timersub(t, duration, &keep_from); ++it; - Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %d packets", + Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %zu packets", streamId, pktQueue.size()); for ( ; it != pktQueue.rend(); ++it) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - if (av_packet->stream_index == streamId - && timercmp( &zm_packet->timestamp, &keep_from, <= )) { - Debug(3, "Found frame before keep time with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec); + if ( + (av_packet->stream_index == streamId) + and + timercmp(zm_packet->timestamp, &keep_from, <=) + ) { + Debug(3, "Found frame before keep time with stream index %d at %" PRIi64 ".%" PRIi64, + av_packet->stream_index, + static_cast(zm_packet->timestamp->tv_sec), + static_cast(zm_packet->timestamp->tv_usec)); break; } } - if (it == pktQueue.rend()) { + if ( it == pktQueue.rend() ) { Debug(1, "Didn't find a frame before queue preserve time. keeping all"); + mutex.unlock(); return 0; } @@ -241,12 +500,15 @@ unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) for ( ; it != pktQueue.rend(); ++it) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - if (av_packet->flags & AV_PKT_FLAG_KEY - && av_packet->stream_index == streamId) { - Debug(3, "Found keyframe before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec ); + if ( + (av_packet->flags & AV_PKT_FLAG_KEY) + and + (av_packet->stream_index == streamId) + ) { + Debug(3, "Found keyframe before start with stream index %d at %" PRIi64 ".%" PRIi64, + av_packet->stream_index, + static_cast(zm_packet->timestamp->tv_sec), + static_cast(zm_packet->timestamp->tv_usec)); break; } } @@ -257,142 +519,280 @@ unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) unsigned int deleted_frames = 0; ZMPacket *zm_packet = nullptr; - while (distance(it, pktQueue.rend()) > 1) { + while ( distance(it, pktQueue.rend()) > 1 ) { zm_packet = pktQueue.front(); + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ( *(*iterator_it) == zm_packet ) { + Debug(4, "Bumping it because it is at the front that we are deleting"); + ++(*iterators_it); + } + } // end foreach iterator pktQueue.pop_front(); packet_counts[zm_packet->packet.stream_index] -= 1; delete zm_packet; deleted_frames += 1; } - zm_packet = nullptr; Debug(3, "Deleted %d frames", deleted_frames); - return deleted_frames; } -unsigned int zm_packetqueue::size() { +unsigned int PacketQueue::size() { return pktQueue.size(); } -int zm_packetqueue::packet_count( int stream_id ) { +int PacketQueue::packet_count(int stream_id) { + if ( stream_id < 0 or stream_id > max_stream_id ) { + Error("Invalid stream_id %d max is %d", stream_id, max_stream_id); + return -1; + } return packet_counts[stream_id]; -} // end int zm_packetqueue::packet_count( int stream_id ) +} // end int PacketQueue::packet_count(int stream_id) -// Clear packets before the given timestamp. -// Must also take into account pre_event_count frames -void zm_packetqueue::clear_unwanted_packets( - timeval *recording_started, - int pre_event_count, - int mVideoStreamId) { - // Need to find the keyframe <= recording_started. Can get rid of audio packets. - if ( pktQueue.empty() ) - return; - // Step 1 - find frame <= recording_started. - // Step 2 - go back pre_event_count - // Step 3 - find a keyframe - // Step 4 - pop packets until we get to the packet in step 3 - std::list::reverse_iterator it; +// Returns a packet. Packet will be locked +ZMLockedPacket *PacketQueue::get_packet(packetqueue_iterator *it) { + if (deleting or zm_terminate) + return nullptr; - // Step 1 - find frame <= recording_started. - Debug(3, "Looking for frame before start (%d.%d) recording stream id (%d), queue has %d packets", - recording_started->tv_sec, recording_started->tv_usec, mVideoStreamId, pktQueue.size()); - for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - ( av_packet->stream_index == mVideoStreamId ) - && - timercmp( &(zm_packet->timestamp), recording_started, <= ) - ) { - Debug(3, "Found frame before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec); - break; + Debug(4, "Locking in get_packet using it %p queue end? %d, packet %p", + std::addressof(*it), (*it == pktQueue.end()), *(*it)); + std::unique_lock lck(mutex); + Debug(4, "Have Lock in get_packet"); + + ZMLockedPacket *lp = nullptr; + while (!lp) { + while (*it == pktQueue.end()) { + if (deleting or zm_terminate) { + Debug(1, "terminated, leaving"); + condition.notify_all(); + return nullptr; + } + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); } - Debug(3, "Not Found frame before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec); + if (deleting or zm_terminate) { + Debug(1, "terminated, leaving"); + condition.notify_all(); + return nullptr; + } + + ZMPacket *p = *(*it); + if (!p) { + Error("Null p?!"); + return nullptr; + } + Debug(4, "get_packet using it %p locking index %d, packet %p", + std::addressof(*it), p->image_index, p); + // Packets are only deleted by packetqueue, so lock must be held. + // We shouldn't have to trylock. Someone else might hold the lock but not for long + + lp = new ZMLockedPacket(p); + if (lp->trylock()) { + Debug(2, "Locked packet %d, unlocking packetqueue mutex", p->image_index); + + return lp; + } + delete lp; + lp = nullptr; + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } // end while !lp + return nullptr; +} // end ZMLockedPacket *PacketQueue::get_packet(it) + +void PacketQueue::unlock(ZMLockedPacket *lp) { + delete lp; + condition.notify_all(); +} + +bool PacketQueue::increment_it(packetqueue_iterator *it) { + Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end())); + if ((*it) == pktQueue.end() or deleting) { + return false; + } + std::unique_lock lck(mutex); + ++(*it); + if (*it != pktQueue.end()) { + Debug(2, "Incrementing %p, %p still not at end, so returning true", it, std::addressof(*it)); + return true; + } + Debug(2, "At end"); + return false; +} // end bool PacketQueue::increment_it(packetqueue_iterator *it) + +// Increment it only considering packets for a given stream +bool PacketQueue::increment_it(packetqueue_iterator *it, int stream_id) { + Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), (*it == pktQueue.end())); + if ( *it == pktQueue.end() ) { + return false; } - if ( it == pktQueue.rend() ) { - Info("Didn't find a frame before event starttime. keeping all"); - return; - } + std::unique_lock lck(mutex); + do { + ++(*it); + } while ( (*it != pktQueue.end()) and ( (*(*it))->packet.stream_index != stream_id) ); - Debug(1, "Seeking back %d frames", pre_event_count); - for ( ; pre_event_count && (it != pktQueue.rend()); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( av_packet->stream_index == mVideoStreamId ) { - --pre_event_count; + if ( *it != pktQueue.end() ) { + Debug(2, "Incrementing %p, still not at end, so incrementing", it); + return true; + } + return false; +} // end bool PacketQueue::increment_it(packetqueue_iterator *it) + +packetqueue_iterator *PacketQueue::get_event_start_packet_it( + packetqueue_iterator snapshot_it, + unsigned int pre_event_count + ) { + std::unique_lock lck(mutex); + + packetqueue_iterator *it = new packetqueue_iterator; + iterators.push_back(it); + + *it = snapshot_it; + ZMPacket *packet = *(*it); + ZM_DUMP_PACKET(packet->packet, ""); + // Step one count back pre_event_count frames as the minimum + // Do not assume that snapshot_it is video + // snapshot it might already point to the beginning + if (pre_event_count) { + while ((*it) != pktQueue.begin()) { + packet = *(*it); + Debug(1, "Previous packet pre_event_count %d stream_index %d keyframe %d score %d", + pre_event_count, packet->packet.stream_index, packet->keyframe, packet->score); + ZM_DUMP_PACKET(packet->packet, ""); + if (packet->packet.stream_index == video_stream_id) { + pre_event_count --; + if (!pre_event_count) + break; + } + (*it)--; } } - - if ( it == pktQueue.rend() ) { - Debug(1, "ran out of pre_event frames before event starttime. keeping all"); - return; - } - - Debug(3, "Looking for keyframe"); - for ( ; it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - if ( - ( av_packet->flags & AV_PKT_FLAG_KEY ) - && - ( av_packet->stream_index == mVideoStreamId ) - ) { - Debug(3, "Found keyframe before start with stream index %d at %d.%d", - av_packet->stream_index, - zm_packet->timestamp.tv_sec, - zm_packet->timestamp.tv_usec ); - break; + // it either points to beginning or we have seen pre_event_count video packets. + + packet = *(*it); + if (pre_event_count) { + if (packet->image_index < (int)pre_event_count) { + // probably just starting up + Debug(1, "Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count); + } else { + Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count); } - } - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); - return; + ZM_DUMP_PACKET(packet->packet, ""); + return it; } - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", - av_packet->stream_index, - ( av_packet->flags & AV_PKT_FLAG_KEY ), - distance( it, pktQueue.rend() ), - pktQueue.size() ); - - unsigned int deleted_frames = 0; - ZMPacket *packet = nullptr; - while ( distance(it, pktQueue.rend()) > 1 ) { - //while ( pktQueue.rend() != it ) { - packet = pktQueue.front(); - pktQueue.pop_front(); - packet_counts[packet->packet.stream_index] -= 1; - delete packet; - deleted_frames += 1; + while ((*it) != pktQueue.begin()) { + packet = *(*it); + ZM_DUMP_PACKET(packet->packet, "No keyframe"); + if ((packet->packet.stream_index == video_stream_id) and packet->keyframe) + return it; // Success + --(*it); } - packet = nullptr; // tidy up for valgrind - - zm_packet = pktQueue.front(); - av_packet = &(zm_packet->packet); - if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { - Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", - deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); - } else { - Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", - deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + if (!(*(*it))->keyframe) { + Warning("Hit beginning of packetqueue and packet is not a keyframe. index is %d", packet->image_index); } -} // end void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) + return it; +} // end packetqueue_iterator *PacketQueue::get_event_start_packet_it -void zm_packetqueue::dumpQueue() { +void PacketQueue::dumpQueue() { std::list::reverse_iterator it; for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - dumpPacket(av_packet); + ZM_DUMP_PACKET(zm_packet->packet, ""); } } + +/* Returns an iterator to the first video keyframe in the queue. + * nullptr if no keyframe video packet exists. + */ +packetqueue_iterator * PacketQueue::get_video_it(bool wait) { + packetqueue_iterator *it = new packetqueue_iterator; + iterators.push_back(it); + + std::unique_lock lck(mutex); + *it = pktQueue.begin(); + + if ( wait ) { + while ( ((! pktQueue.size()) or (*it == pktQueue.end())) and !zm_terminate and !deleting ) { + Debug(2, "waiting for packets in queue. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + *it = pktQueue.begin(); + } + if ( deleting or zm_terminate ) { + free_it(it); + delete it; + return nullptr; + } + } + + while ( *it != pktQueue.end() ) { + ZMPacket *zm_packet = *(*it); + if (!zm_packet) { + Error("Null zmpacket in queue!?"); + free_it(it); + return nullptr; + } + Debug(1, "Packet keyframe %d for stream %d, so returning the it to it", + zm_packet->keyframe, zm_packet->packet.stream_index); + if (zm_packet->keyframe and ( zm_packet->packet.stream_index == video_stream_id )) { + Debug(1, "Found a keyframe for stream %d, so returning the it to it", video_stream_id); + return it; + } + ++(*it); + } + Debug(1, "DIdn't Found a keyframe for stream %d, so returning the it to it", video_stream_id); + return it; +} // get video_it + +void PacketQueue::free_it(packetqueue_iterator *it) { + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + if ( *iterators_it == it ) { + iterators.erase(iterators_it); + break; + } + } +} + +bool PacketQueue::is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet) { + for ( + std::list::iterator iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + if ( *iterator_it == pktQueue.end() ) { + continue; + } + Debug(4, "Checking iterator %p == packet ? %d", std::addressof(*iterator_it), ( *(*iterator_it) == zm_packet )); + // Have to check each iterator and make sure it doesn't point to the packet we are about to delete + if ( *(*iterator_it) == zm_packet ) { + return true; + } + } // end foreach iterator + return false; +} + +void PacketQueue::setMaxVideoPackets(int p) { + max_video_packet_count = p; + Debug(1, "Setting max_video_packet_count to %d", p); + if ( max_video_packet_count < 0 ) + max_video_packet_count = 0 ; +} +void PacketQueue::setPreEventVideoPackets(int p) { + pre_event_video_packet_count = p; + Debug(1, "Setting pre_event_video_packet_count to %d", p); + if ( pre_event_video_packet_count < 1 ) + pre_event_video_packet_count = 1; + // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 31fe321cb..bc9932f24 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -16,41 +16,75 @@ //You should have received a copy of the GNU General Public License //along with ZoneMinder. If not, see . - #ifndef ZM_PACKETQUEUE_H #define ZM_PACKETQUEUE_H -//#include -//#include -//#include +#include #include -#include "zm_packet.h" +#include -extern "C" { -#include -} -class zm_packetqueue { -public: - zm_packetqueue(int max_stream_id); - virtual ~zm_packetqueue(); - bool queuePacket(AVPacket* packet, struct timeval *timestamp); - bool queuePacket(ZMPacket* packet); - bool queuePacket(AVPacket* packet); - ZMPacket * popPacket(); - bool popVideoPacket(ZMPacket* packet); - bool popAudioPacket(ZMPacket* packet); - unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id); - unsigned int clearQueue(struct timeval *duration, int streamid); - void clearQueue(); - void dumpQueue(); - unsigned int size(); - void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId); - int packet_count(int stream_id); -private: +class ZMPacket; +class ZMLockedPacket; + +typedef std::list::iterator packetqueue_iterator; + +class PacketQueue { + public: // For now just to ease development std::list pktQueue; + std::list::iterator analysis_it; + + int video_stream_id; + int max_video_packet_count; // allow a negative value to someday mean unlimited + // This is now a hard limit on the # of video packets to keep in the queue so that we can limit ram + int pre_event_video_packet_count; // Was max_video_packet_count int max_stream_id; int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */ + bool deleting; + bool keep_keyframes; + std::list iterators; + std::mutex mutex; + std::condition_variable condition; + + public: + PacketQueue(); + virtual ~PacketQueue(); + std::list::const_iterator end() const { return pktQueue.end(); } + std::list::const_iterator begin() const { return pktQueue.begin(); } + + int addStream(); + void setMaxVideoPackets(int p); + void setPreEventVideoPackets(int p); + void setKeepKeyframes(bool k) { keep_keyframes = k; }; + + bool queuePacket(ZMPacket* packet); + ZMLockedPacket * popPacket(); + bool popVideoPacket(ZMPacket* packet); + bool popAudioPacket(ZMPacket* packet); + unsigned int clear(unsigned int video_frames_to_keep, int stream_id); + unsigned int clear(struct timeval *duration, int streamid); + void clear(); + void dumpQueue(); + unsigned int size(); + unsigned int get_packet_count(int stream_id) const { return packet_counts[stream_id]; }; + + void clear_unwanted_packets(timeval *recording, int pre_event_count, int mVideoStreamId); + void clearPackets(ZMPacket *); + int packet_count(int stream_id); + + bool increment_it(packetqueue_iterator *it); + bool increment_it(packetqueue_iterator *it, int stream_id); + ZMLockedPacket *get_packet(packetqueue_iterator *); + packetqueue_iterator *get_video_it(bool wait); + packetqueue_iterator *get_stream_it(int stream_id); + void free_it(packetqueue_iterator *); + + packetqueue_iterator *get_event_start_packet_it( + packetqueue_iterator snapshot_it, + unsigned int pre_event_count + ); + bool is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet); + void unlock(ZMLockedPacket *lp); }; #endif /* ZM_PACKETQUEUE_H */ diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index 35319d6af..94f153b98 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -17,14 +17,9 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_poly.h" -#ifndef SOLARIS -#include -#else #include -#endif void Polygon::calcArea() { double float_area = 0.0L; @@ -46,11 +41,10 @@ void Polygon::calcCentre() { } float_x /= (6*area); float_y /= (6*area); - //printf( "%.2f,%.2f\n", float_x, float_y ); centre = Coord( (int)round(float_x), (int)round(float_y) ); } -Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords( p_n_coords ) { +Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords(p_n_coords) { coords = new Coord[n_coords]; int min_x = -1; @@ -73,7 +67,7 @@ Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords( p_n_coords ) calcCentre(); } -Polygon::Polygon( const Polygon &p_polygon ) : +Polygon::Polygon(const Polygon &p_polygon) : n_coords(p_polygon.n_coords), extent(p_polygon.extent), area(p_polygon.area), @@ -85,19 +79,20 @@ Polygon::Polygon( const Polygon &p_polygon ) : } } -Polygon &Polygon::operator=( const Polygon &p_polygon ) { - if ( n_coords < p_polygon.n_coords ) { - delete[] coords; - coords = new Coord[p_polygon.n_coords]; - } +Polygon &Polygon::operator=(const Polygon &p_polygon) { n_coords = p_polygon.n_coords; - for ( int i = 0; i < n_coords; i++ ) { - coords[i] = p_polygon.coords[i]; + + Coord *new_coords = new Coord[n_coords]; + for (int i = 0; i < n_coords; i++) { + new_coords[i] = p_polygon.coords[i]; } + delete[] coords; + coords = new_coords; + extent = p_polygon.extent; area = p_polygon.area; centre = p_polygon.centre; - return *this ; + return *this; } bool Polygon::isInside( const Coord &coord ) const { diff --git a/src/zm_poly.h b/src/zm_poly.h index 53eafcf84..21b7f14bb 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -20,11 +20,9 @@ #ifndef ZM_POLY_H #define ZM_POLY_H -#include "zm.h" -#include "zm_coord.h" #include "zm_box.h" -#include +class Coord; // // Class used for storing a box, which is defined as a region @@ -74,8 +72,6 @@ protected: Box extent; int area; Coord centre; - Edge *edges; - Slice *slices; protected: void initialiseEdges(); @@ -83,7 +79,7 @@ protected: void calcCentre(); public: - inline Polygon() : n_coords(0), coords(nullptr), area(0), edges(nullptr), slices(nullptr) { + inline Polygon() : n_coords(0), coords(nullptr), area(0) { } Polygon(int p_n_coords, const Coord *p_coords); Polygon(const Polygon &p_polygon); @@ -100,9 +96,13 @@ public: inline const Box &Extent() const { return extent; } inline int LoX() const { return extent.LoX(); } + inline int LoX(int p_lo_x) { return extent.LoX(p_lo_x); } inline int HiX() const { return extent.HiX(); } + inline int HiX(int p_hi_x) { return extent.HiX(p_hi_x); } inline int LoY() const { return extent.LoY(); } + inline int LoY(int p_lo_y) { return extent.LoY(p_lo_y); } inline int HiY() const { return extent.HiY(); } + inline int HiY(int p_hi_y) { return extent.HiY(p_hi_y); } inline int Width() const { return extent.Width(); } inline int Height() const { return extent.Height(); } diff --git a/src/zm_regexp.cpp b/src/zm_regexp.cpp index fa3bc2de7..1541b31ef 100644 --- a/src/zm_regexp.cpp +++ b/src/zm_regexp.cpp @@ -15,13 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +*/ -#include - -#include "zm.h" #include "zm_regexp.h" +#include "zm_logger.h" +#include + #if HAVE_LIBPCRE RegExpr::RegExpr( const char *pattern, int flags, int p_max_matches ) : max_matches( p_max_matches ), match_buffers( nullptr ), match_lengths( nullptr ), match_valid( nullptr ) diff --git a/src/zm_regexp.h b/src/zm_regexp.h index c859823d5..9c9ec5675 100644 --- a/src/zm_regexp.h +++ b/src/zm_regexp.h @@ -17,11 +17,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "zm.h" - #ifndef ZM_REGEXP_H #define ZM_REGEXP_H +#include "zm_config.h" + #if HAVE_LIBPCRE #if HAVE_PCRE_H @@ -51,7 +51,7 @@ protected: bool ok; public: - RegExpr( const char *pattern, int cflags=0, int p_max_matches=32 ); + explicit RegExpr( const char *pattern, int cflags=0, int p_max_matches=32 ); ~RegExpr(); bool Ok() const { return( ok ); } int MatchCount() const { return( n_matches ); } diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index dce4daf5c..5e3dfefae 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -20,9 +20,12 @@ #include "zm_remote_camera.h" #include "zm_utils.h" +#include +#include +#include RemoteCamera::RemoteCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, @@ -37,7 +40,7 @@ RemoteCamera::RemoteCamera( bool p_capture, bool p_record_audio ) : - Camera( p_monitor_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), + Camera( monitor, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), protocol( p_protocol ), host( p_host ), port( p_port ), @@ -80,7 +83,7 @@ void RemoteCamera::Initialise() { if ( authIndex != std::string::npos ) { auth = host.substr( 0, authIndex ); host.erase( 0, authIndex+1 ); - auth64 = base64Encode( auth ); + auth64 = Base64Encode(auth); authIndex = auth.rfind( ':' ); username = auth.substr(0,authIndex); diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index 63396df95..b4d9af3bc 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -22,12 +22,7 @@ #include "zm_camera.h" #include "zm_rtsp_auth.h" - #include -#include -#include -#include -#include #define SOCKET_BUF_SIZE 8192 @@ -57,7 +52,7 @@ protected: public: RemoteCamera( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_proto, const std::string &p_host, const std::string &p_port, @@ -74,24 +69,23 @@ public: ); virtual ~RemoteCamera(); - const std::string &Protocol() const { return( protocol ); } - const std::string &Host() const { return( host ); } - const std::string &Port() const { return( port ); } - const std::string &Path() const { return( path ); } - const std::string &Auth() const { return( auth ); } - const std::string &Username() const { return( username ); } - const std::string &Password() const { return( password ); } + const std::string &Protocol() const { return protocol; } + const std::string &Host() const { return host; } + const std::string &Port() const { return port; } + const std::string &Path() const { return path; } + const std::string &Auth() const { return auth; } + const std::string &Username() const { return username; } + const std::string &Password() const { return password; } virtual void Initialise(); virtual void Terminate() = 0; virtual int Connect() = 0; virtual int Disconnect() = 0; - virtual int PreCapture() { return 0; }; - virtual int PrimeCapture() { return 0; }; - virtual int Capture( Image &image ) = 0; - virtual int PostCapture() = 0; - virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0; - int Read( int fd, char*buf, int size ); + virtual int PreCapture() override { return 0; }; + virtual int PrimeCapture() override { return 0; }; + virtual int Capture(ZMPacket &p) override = 0; + virtual int PostCapture() override = 0; + int Read(int fd, char*buf, int size); }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 50c19cc44..6eab03fb8 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -18,15 +18,16 @@ // #include "zm_remote_camera_http.h" -#include "zm_rtsp_auth.h" -#include "zm_mem_utils.h" +#include "zm_monitor.h" +#include "zm_packet.h" #include "zm_signal.h" - -#include -#include -#include +#include "zm_regexp.h" +#include "zm_utils.h" +#include #include +#include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -44,7 +45,7 @@ static RegExpr *content_type_expr = nullptr; #endif RemoteCameraHttp::RemoteCameraHttp( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_method, const std::string &p_host, const std::string &p_port, @@ -58,7 +59,7 @@ RemoteCameraHttp::RemoteCameraHttp( bool p_capture, bool p_record_audio ) : RemoteCamera( - p_monitor_id, + monitor, "http", p_host, p_port, @@ -83,10 +84,11 @@ RemoteCameraHttp::RemoteCameraHttp( else if ( p_method == "regexp" ) { method = REGEXP; } else - Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); + Fatal("Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor->Id()); if ( capture ) { Initialise(); } + mVideoStream = nullptr; } RemoteCameraHttp::~RemoteCameraHttp() { @@ -103,7 +105,7 @@ void RemoteCameraHttp::Initialise() { request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); + request += "Connection: Keep-Alive\r\n"; if ( !auth.empty() ) request += stringtf( "Authorization: Basic %s\r\n", auth64.c_str() ); request += "\r\n"; @@ -157,7 +159,7 @@ int RemoteCameraHttp::Connect() { addr = (struct sockaddr_in *)p->ai_addr; inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN ); - Warning("Can't connect to remote camera mid: %d at %s: %s", monitor_id, buf, strerror(errno) ); + Warning("Can't connect to remote camera mid: %d at %s: %s", monitor->Id(), buf, strerror(errno)); continue; } @@ -200,7 +202,7 @@ int RemoteCameraHttp::SendRequest() { * > 0 is the # of bytes read. */ -int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { +int RemoteCameraHttp::ReadData(Buffer &buffer, unsigned int bytes_expected) { fd_set rfds; FD_ZERO(&rfds); FD_SET(sd, &rfds); @@ -208,44 +210,49 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { struct timeval temp_timeout = timeout; int n_found = select(sd+1, &rfds, nullptr, nullptr, &temp_timeout); - if( n_found == 0 ) { - Debug( 1, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); + if (n_found == 0) { + Debug(1, "Select timed out timeout was %" PRIi64 " secs %" PRIi64" usecs", + static_cast(temp_timeout.tv_sec), + static_cast(temp_timeout.tv_usec)); int error = 0; - socklen_t len = sizeof (error); - int retval = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); - if(retval != 0 ) { - Debug( 1, "error getting socket error code %s", strerror(retval) ); + socklen_t len = sizeof(error); + int retval = getsockopt(sd, SOL_SOCKET, SO_ERROR, &error, &len); + if (retval != 0) { + Debug(1, "error getting socket error code %s", strerror(retval)); } - if (error != 0 ) { + if (error != 0) { return -1; } // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. //Disconnect(); return 0; - } else if ( n_found < 0) { + } else if (n_found == EINTR) { + Error("Select interrupted"); + return 0; + } else if (n_found < 0) { Error("Select error: %s", strerror(errno)); return -1; } unsigned int total_bytes_to_read = 0; - if ( bytes_expected ) { + if (bytes_expected) { total_bytes_to_read = bytes_expected; } else { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { - Error( "Can't ioctl(): %s", strerror(errno) ); + if (ioctl(sd, FIONREAD, &total_bytes_to_read) < 0) { + Error("Can't ioctl(): %s", strerror(errno) ); return -1; } - if ( total_bytes_to_read == 0 ) { - if ( mode == SINGLE_IMAGE ) { + if (total_bytes_to_read == 0) { + if (mode == SINGLE_IMAGE) { int error = 0; - socklen_t len = sizeof (error); - int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); - if ( retval != 0 ) { - Debug( 1, "error getting socket error code %s", strerror(retval) ); + socklen_t len = sizeof(error); + int retval = getsockopt(sd, SOL_SOCKET, SO_ERROR, &error, &len); + if (retval != 0) { + Debug(1, "error getting socket error code %s", strerror(retval)); } - if ( error != 0 ) { + if (error != 0) { return -1; } // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. @@ -259,44 +266,44 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { } // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. - if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { + if (total_bytes_to_read > ZM_NETWORK_BUFSIZ) { total_bytes_to_read = ZM_NETWORK_BUFSIZ; - Debug(3, "Just getting 32K" ); + Debug(4, "Just getting 32K"); } else { - Debug(3, "Just getting %d", total_bytes_to_read ); + Debug(4, "Just getting %d", total_bytes_to_read); } } // end if bytes_expected or not - Debug( 3, "Expecting %d bytes", total_bytes_to_read ); + Debug(4, "Expecting %d bytes", total_bytes_to_read); int total_bytes_read = 0; do { - int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0 ) { - Error( "Read error: %s", strerror(errno) ); - return( -1 ); - } else if ( bytes_read == 0 ) { - Debug( 2, "Socket closed" ); + int bytes_read = buffer.read_into(sd, total_bytes_to_read); + if (bytes_read < 0) { + Error("Read error: %s", strerror(errno)); + return -1; + } else if (bytes_read == 0) { + Debug(2, "Socket closed"); //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); - } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { - Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); - return( -1 ); + return -1; + } else if ((unsigned int)bytes_read < total_bytes_to_read) { + Debug(1, "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read); + } else { + Debug(3, "Read %d bytes", bytes_read); } - Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } while ( total_bytes_to_read ); + } while (total_bytes_to_read); - Debug(4, buffer); + Debug(4, "buffer size: %d", static_cast(buffer)); return total_bytes_read; -} +} // end readData int RemoteCameraHttp::GetData() { time_t start_time = time(nullptr); int buffer_len = 0; - while ( !( buffer_len = ReadData(buffer) ) ) { - if ( zm_terminate || ( start_time - time(nullptr) < ZM_WATCH_MAX_DELAY )) + while (!(buffer_len = ReadData(buffer))) { + if (zm_terminate or ( (time(nullptr) - start_time) > ZM_WATCH_MAX_DELAY )) return -1; Debug(4, "Timeout waiting for REGEXP HEADER"); usleep(100000); @@ -305,7 +312,6 @@ int RemoteCameraHttp::GetData() { } int RemoteCameraHttp::GetResponse() { - int buffer_len; #if HAVE_LIBPCRE if ( method == REGEXP ) { const char *header = nullptr; @@ -326,7 +332,7 @@ int RemoteCameraHttp::GetResponse() { switch( state ) { case HEADER : { - buffer_len = GetData(); + int buffer_len = GetData(); if ( buffer_len < 0 ) { Error("Unable to read header data"); return -1; @@ -337,8 +343,7 @@ int RemoteCameraHttp::GetResponse() { header_len = header_expr->MatchLength( 1 ); Debug(4, "Captured header (%d bytes):\n'%s'", header_len, header); - if ( status_expr->Match( header, header_len ) < 4 ) - { + if ( status_expr->Match( header, header_len ) < 4 ) { Error( "Unable to extract HTTP status from header" ); return( -1 ); } @@ -361,7 +366,7 @@ int RemoteCameraHttp::GetResponse() { request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); + request += "Connection: Keep-Alive\r\n"; request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); request += "\r\n"; @@ -375,451 +380,25 @@ int RemoteCameraHttp::GetResponse() { } Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); - if ( connection_expr->Match( header, header_len ) == 2 ) - { + if ( connection_expr->Match( header, header_len ) == 2 ) { connection_type = connection_expr->MatchString( 1 ); Debug( 3, "Got connection '%s'", connection_type ); } - if ( content_length_expr->Match( header, header_len ) == 2 ) - { + if ( content_length_expr->Match( header, header_len ) == 2 ) { content_length = atoi( content_length_expr->MatchString( 1 ) ); Debug( 3, "Got content length '%d'", content_length ); } - if ( content_type_expr->Match( header, header_len ) >= 2 ) - { + if ( content_type_expr->Match( header, header_len ) >= 2 ) { content_type = content_type_expr->MatchString( 1 ); Debug( 3, "Got content type '%s'\n", content_type ); - if ( content_type_expr->MatchCount() > 2 ) - { + if ( content_type_expr->MatchCount() > 2 ) { content_boundary = content_type_expr->MatchString( 2 ); Debug( 3, "Got content boundary '%s'", content_boundary ); } } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", header ); - return( -1 ); - } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ - //// MPEG stream, coming soon! - //} - else - { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); - } - buffer.consume( header_len ); - } - else - { - Debug( 3, "Unable to extract header from stream, retrying" ); - //return( -1 ); - } - break; - } - case SUBHEADER : - { - static RegExpr *subheader_expr = nullptr; - static RegExpr *subcontent_length_expr = nullptr; - static RegExpr *subcontent_type_expr = nullptr; - - if ( !subheader_expr ) - { - char subheader_pattern[256] = ""; - snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); - subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); - } - if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) - { - subheader = subheader_expr->MatchString( 1 ); - subheader_len = subheader_expr->MatchLength( 1 ); - Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - - if ( !subcontent_length_expr ) - subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) - { - content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); - Debug( 3, "Got subcontent length '%d'", content_length ); - } - - if ( !subcontent_type_expr ) - subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) - { - content_type = subcontent_type_expr->MatchString( 1 ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } - - buffer.consume( subheader_len ); - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to extract subheader data" ); - return( -1 ); - } - bytes += buffer_len; - } - break; - } - case CONTENT : - { - - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( (char *)content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( content_length ) - { - while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) - { - Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - int bytes_read = GetData(); - - if ( bytes_read < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } - bytes += bytes_read; - } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); - } - else - { - while ( !content_length ) - { - buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } - bytes += buffer_len; - static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) - { - if ( !content_expr ) - { - char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); - } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) - { - content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); - } - } - } - } - if ( mode == SINGLE_IMAGE ) { - state = HEADER; - Disconnect(); - } else { - state = SUBHEADER; - } - Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return content_length; - } - case HEADERCONT : - case SUBHEADERCONT : - { - // Ignore - break; - } - } - } - } else -#endif // HAVE_LIBPCRE - { - static const char *http_match = "HTTP/"; - static const char *connection_match = "Connection:"; - static const char *content_length_match = "Content-length:"; - static const char *content_type_match = "Content-type:"; - static const char *boundary_match = "boundary="; - static const char *authenticate_match = "WWW-Authenticate:"; - static int http_match_len = 0; - static int connection_match_len = 0; - static int content_length_match_len = 0; - static int content_type_match_len = 0; - static int boundary_match_len = 0; - static int authenticate_match_len = 0; - - if ( !http_match_len ) - http_match_len = strlen( http_match ); - if ( !connection_match_len ) - connection_match_len = strlen( connection_match ); - if ( !content_length_match_len ) - content_length_match_len = strlen( content_length_match ); - if ( !content_type_match_len ) - content_type_match_len = strlen( content_type_match ); - if ( !boundary_match_len ) - boundary_match_len = strlen( boundary_match ); - if ( !authenticate_match_len ) - authenticate_match_len = strlen( authenticate_match ); - - static int n_headers; - //static char *headers[32]; - - static int n_subheaders; - //static char *subheaders[32]; - - static char *http_header; - static char *connection_header; - static char *content_length_header; - static char *content_type_header; - static char *boundary_header; - static char *authenticate_header; - static char subcontent_length_header[33]; - static char subcontent_type_header[65]; - - static char http_version[16]; - static char status_code[16]; - static char status_mesg[256]; - static char connection_type[32]; - static int content_length; - static char content_type[32]; - static char content_boundary[64]; - static int content_boundary_len; - - while ( !zm_terminate ) { - switch( state ) { - case HEADER : - { - n_headers = 0; - http_header = nullptr; - connection_header = nullptr; - content_length_header = nullptr; - content_type_header = nullptr; - authenticate_header = nullptr; - - http_version[0] = '\0'; - status_code [0]= '\0'; - status_mesg [0]= '\0'; - connection_type [0]= '\0'; - content_length = 0; - content_type[0] = '\0'; - content_boundary[0] = '\0'; - content_boundary_len = 0; - } - case HEADERCONT : - { - buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error("Unable to read header"); - return -1; - } - bytes += buffer_len; - - char *crlf = nullptr; - char *header_ptr = (char *)buffer; - int header_len = buffer.size(); - bool all_headers = false; - - while( true ) { - int crlf_len = memspn(header_ptr, "\r\n", header_len); - if ( n_headers ) { - if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { - Debug(3, "Have double linefeed, done headers"); - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - if ( crlf_len ) { - if ( header_len == crlf_len ) { - break; - } else { - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - } - - Debug( 6, "%s", header_ptr ); - if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { - //headers[n_headers++] = header_ptr; - n_headers++; - - if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { - http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); - } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { - connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); - } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { - content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); - } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { - authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); - } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { - content_type_header = header_ptr+content_type_match_len; - Debug( 6, "Got content type header '%s'", header_ptr ); - } else { - Debug( 6, "Got ignored header '%s'", header_ptr ); - } - header_ptr = crlf; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } else { - // No end of line found - break; - } - } // end while search for headers - - if ( all_headers ) { - char *start_ptr, *end_ptr; - - if ( !http_header ) { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - - start_ptr = http_header; - end_ptr = start_ptr+strspn( start_ptr, "10." ); - - // FIXME Why are we memsetting every time? Can we not do it once? - memset( http_version, 0, sizeof(http_version) ); - strncpy( http_version, start_ptr, end_ptr-start_ptr ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); - - memset( status_code, 0, sizeof(status_code) ); - strncpy( status_code, start_ptr, end_ptr-start_ptr ); - int status = atoi( status_code ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - strcpy( status_mesg, start_ptr ); - - if ( status == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); - return( -1 ); - } - if ( ! authenticate_header ) { - Error( "Failed authentication, but don't have an authentication header: " ); - return( -1 ); - } - mNeedAuth = true; - std::string Header = authenticate_header; - Debug(2, "Checking for digest auth in %s", authenticate_header ); - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); - } else { - Debug( 2, "Need some other kind of Authentication" ); - } - } else if ( status < 200 || status > 299 ) { - Error( "Invalid response status %s: %s", status_code, status_mesg ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); - - if ( connection_header ) { - memset( connection_type, 0, sizeof(connection_type) ); - start_ptr = connection_header + strspn( connection_header, " " ); - // FIXME Should we not use strncpy? - strcpy( connection_type, start_ptr ); - Debug( 3, "Got connection '%s'", connection_type ); - } - if ( content_length_header ) { - start_ptr = content_length_header + strspn( content_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got content length '%d'", content_length ); - } - if ( content_type_header ) { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = content_type_header + strspn( content_type_header, " " ); - if ( (end_ptr = strchr( start_ptr, ';' )) ) { - strncpy( content_type, start_ptr, end_ptr-start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - - start_ptr = end_ptr + strspn( end_ptr, "; " ); - - if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { - start_ptr += boundary_match_len; - start_ptr += strspn( start_ptr, "-" ); - content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } else { - Error( "No content boundary found in header '%s'", content_type_header ); - } - } else { - strcpy( content_type, start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - } - } // end if content_type_header - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { // Single image mode = SINGLE_IMAGE; @@ -838,7 +417,7 @@ int RemoteCameraHttp::GetResponse() { } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { // Image stream, so start processing if ( !content_boundary[0] ) { - Error( "No content boundary found in header '%s'", content_type_header ); + Error( "No content boundary found in header '%s'", header ); return( -1 ); } mode = MULTI_IMAGE; @@ -849,8 +428,396 @@ int RemoteCameraHttp::GetResponse() { //// MPEG stream, coming soon! //} else { - Error( "Unrecognised content type '%s'", content_type ); + Error("Unrecognised content type '%s'", content_type); + return -1; + } + buffer.consume(header_len); + } else { + Debug(3, "Unable to extract header from stream, retrying"); + //return( -1 ); + } + break; + } + case SUBHEADER : + { + static RegExpr *subheader_expr = nullptr; + static RegExpr *subcontent_length_expr = nullptr; + static RegExpr *subcontent_type_expr = nullptr; + + if ( !subheader_expr ) { + char subheader_pattern[256] = ""; + snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); + subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); + } + if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { + subheader = subheader_expr->MatchString( 1 ); + subheader_len = subheader_expr->MatchLength( 1 ); + Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); + + if ( !subcontent_length_expr ) + subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) { + content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); + Debug( 3, "Got subcontent length '%d'", content_length ); + } + + if ( !subcontent_type_expr ) + subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) { + content_type = subcontent_type_expr->MatchString( 1 ); + Debug( 3, "Got subcontent type '%s'", content_type ); + } + + buffer.consume( subheader_len ); + state = CONTENT; + } else { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + int buffer_len = GetData(); + if ( buffer_len < 0 ) { + Error( "Unable to extract subheader data" ); return( -1 ); + } + bytes += buffer_len; + } + break; + } + case CONTENT : + { + + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( (char *)content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + format = JPEG; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + format = X_RGB; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + format = X_RGBZ; + } else { + Error( "Found unsupported content type '%s'", content_type ); + return( -1 ); + } + + if (content_length) { + while (((long)buffer.size() < content_length ) and !zm_terminate) { + Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length); + int bytes_read = ReadData(buffer, content_length - buffer.size()); + + if (bytes_read < 0) { + Error("Unable to read content"); + return -1; + } + bytes += bytes_read; + } + Debug(3, "Got end of image by length, content-length = %d", content_length); + } else { + while (!content_length) { + int buffer_len = GetData(); + if (buffer_len < 0) { + Error("Unable to read content"); + return -1; + } + bytes += buffer_len; + static RegExpr *content_expr = 0; + if (mode == MULTI_IMAGE) { + if (!content_expr) { + char content_pattern[256] = ""; + snprintf(content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary); + content_expr = new RegExpr(content_pattern, PCRE_DOTALL); + } + if (content_expr->Match( buffer, buffer.size()) == 2) { + content_length = content_expr->MatchLength( 1 ); + Debug(3, "Got end of image by pattern, content-length = %d", content_length); + } + } + } + } + if ( mode == SINGLE_IMAGE ) { + state = HEADER; + Disconnect(); + } else { + state = SUBHEADER; + } + Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); + return content_length; + } + case HEADERCONT : + case SUBHEADERCONT : + // Ignore + break; + } + } + } else +#endif // HAVE_LIBPCRE + { + static const char *http_match = "HTTP/"; + static const char *connection_match = "Connection:"; + static const char *content_length_match = "Content-length:"; + static const char *content_type_match = "Content-type:"; + static const char *boundary_match = "boundary="; + static const char *authenticate_match = "WWW-Authenticate:"; + static int http_match_len = 0; + static int connection_match_len = 0; + static int content_length_match_len = 0; + static int content_type_match_len = 0; + static int boundary_match_len = 0; + static int authenticate_match_len = 0; + + if ( !http_match_len ) + http_match_len = strlen(http_match); + if ( !connection_match_len ) + connection_match_len = strlen(connection_match); + if ( !content_length_match_len ) + content_length_match_len = strlen(content_length_match); + if ( !content_type_match_len ) + content_type_match_len = strlen(content_type_match); + if ( !boundary_match_len ) + boundary_match_len = strlen(boundary_match); + if ( !authenticate_match_len ) + authenticate_match_len = strlen(authenticate_match); + + static int n_headers; + static int n_subheaders; + + static char *http_header; + static char *connection_header; + static char *content_length_header; + static char *content_type_header; + static char *boundary_header; + static char *authenticate_header; + static char subcontent_length_header[33]; + static char subcontent_type_header[65]; + + static char http_version[16]; + static char status_code[16]; + static char status_mesg[256]; + static char connection_type[32]; + static int content_length; + static char content_type[32]; + static char content_boundary[64]; + static int content_boundary_len; + + while (!zm_terminate) { + switch (state) { + case HEADER : + n_headers = 0; + http_header = nullptr; + connection_header = nullptr; + content_length_header = nullptr; + content_type_header = nullptr; + authenticate_header = nullptr; + + http_version[0] = '\0'; + status_code [0]= '\0'; + status_mesg [0]= '\0'; + connection_type [0]= '\0'; + content_length = 0; + content_type[0] = '\0'; + content_boundary[0] = '\0'; + content_boundary_len = 0; + FALLTHROUGH; + case HEADERCONT : + { + int buffer_len = GetData(); + if (buffer_len < 0) { + Error("Unable to read header"); + return -1; + } + bytes += buffer_len; + + char *crlf = nullptr; + char *header_ptr = buffer; + int header_len = buffer.size(); + bool all_headers = false; + + while (!zm_terminate) { + int crlf_len = memspn(header_ptr, "\r\n", header_len); + if (n_headers) { + if ( + (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len)) + || + (crlf_len == 4 && !strncmp(header_ptr, "\r\n\r\n", crlf_len)) + ) { + Debug(3, "Have double linefeed, done headers"); + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume(header_ptr-(char *)buffer); + all_headers = true; + break; + } + } + if (crlf_len) { + if (header_len == crlf_len) { + break; + } else { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume(header_ptr-(char *)buffer); + } + } + + Debug(6, "%s", header_ptr); + if ((crlf = mempbrk(header_ptr, "\r\n", header_len))) { + //headers[n_headers++] = header_ptr; + n_headers++; + + if (!http_header && (strncasecmp(header_ptr, http_match, http_match_len) == 0)) { + http_header = header_ptr+http_match_len; + Debug(6, "Got http header '%s'", header_ptr); + } else if ( !connection_header && (strncasecmp(header_ptr, connection_match, connection_match_len) == 0) ) { + connection_header = header_ptr+connection_match_len; + Debug(6, "Got connection header '%s'", header_ptr); + } else if ( !content_length_header && (strncasecmp(header_ptr, content_length_match, content_length_match_len) == 0) ) { + content_length_header = header_ptr+content_length_match_len; + Debug(6, "Got content length header '%s'", header_ptr); + } else if ( !authenticate_header && (strncasecmp(header_ptr, authenticate_match, authenticate_match_len) == 0) ) { + authenticate_header = header_ptr; + Debug(6, "Got authenticate header '%s'", header_ptr); + } else if ( !content_type_header && (strncasecmp(header_ptr, content_type_match, content_type_match_len) == 0) ) { + content_type_header = header_ptr+content_type_match_len; + Debug(6, "Got content type header '%s'", header_ptr); + } else { + Debug(6, "Got ignored header '%s'", header_ptr); + } + header_ptr = crlf; + header_len -= buffer.consume(header_ptr-(char *)buffer); + } else { + // No end of line found + break; + } + } // end while search for headers + + if (all_headers) { + char *start_ptr, *end_ptr; + + if (!http_header) { + Error("Unable to extract HTTP status from header"); + return -1; + } + + start_ptr = http_header; + end_ptr = start_ptr+strspn(start_ptr, "10."); + + // FIXME Why are we memsetting every time? Can we not do it once? + //memset(http_version, 0, sizeof(http_version)); + strncpy(http_version, start_ptr, end_ptr-start_ptr); + + start_ptr = end_ptr; + start_ptr += strspn(start_ptr, " "); + end_ptr = start_ptr+strspn(start_ptr, "0123456789"); + + memset(status_code, 0, sizeof(status_code)); + strncpy(status_code, start_ptr, end_ptr-start_ptr); + int status = atoi(status_code); + + start_ptr = end_ptr; + start_ptr += strspn(start_ptr, " "); + strcpy(status_mesg, start_ptr); + + if (status == 401) { + if (mNeedAuth) { + Error("Failed authentication"); + return -1; + } + if (!authenticate_header) { + Error("Failed authentication, but don't have an authentication header."); + return -1; + } + mNeedAuth = true; + std::string Header = authenticate_header; + Debug(2, "Checking for digest auth in %s", authenticate_header); + + mAuthenticator->checkAuthResponse(Header); + if (mAuthenticator->auth_method() == zm::AUTH_DIGEST) { + Debug(2, "Need Digest Authentication"); + request = stringtf("GET %s HTTP/%s\r\n", path.c_str(), config.http_version); + request += stringtf("User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION); + request += stringtf("Host: %s\r\n", host.c_str()); + if ( strcmp(config.http_version, "1.0") == 0 ) + request += "Connection: Keep-Alive\r\n"; + request += mAuthenticator->getAuthHeader("GET", path.c_str()); + request += "\r\n"; + + Debug(2, "New request header: %s", request.c_str()); + return 0; + } else { + Debug(2, "Need some other kind of Authentication"); + } + } else if (status < 200 || status > 299) { + Error("Invalid response status %s: %s", status_code, status_mesg); + return -1; + } + Debug(3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version); + + if (connection_header) { + memset(connection_type, 0, sizeof(connection_type)); + start_ptr = connection_header + strspn(connection_header, " "); + // FIXME Should we not use strncpy? + strcpy(connection_type, start_ptr); + Debug(3, "Got connection '%s'", connection_type); + } + if (content_length_header) { + start_ptr = content_length_header + strspn(content_length_header, " "); + content_length = atoi(start_ptr); + Debug(3, "Got content length '%d'", content_length); + } + if (content_type_header) { + //memset(content_type, 0, sizeof(content_type)); + start_ptr = content_type_header + strspn(content_type_header, " "); + if ( (end_ptr = strchr(start_ptr, ';')) ) { + strncpy(content_type, start_ptr, end_ptr-start_ptr); + Debug(3, "Got content type '%s'", content_type); + + start_ptr = end_ptr + strspn(end_ptr, "; "); + + if (strncasecmp(start_ptr, boundary_match, boundary_match_len) == 0) { + start_ptr += boundary_match_len; + start_ptr += strspn(start_ptr, "-"); + content_boundary_len = sprintf(content_boundary, "--%s", start_ptr); + Debug(3, "Got content boundary '%s'", content_boundary); + } else { + Error("No content boundary found in header '%s'", content_type_header); + } + } else { + strcpy(content_type, start_ptr); + Debug(3, "Got content type '%s'", content_type); + } + } // end if content_type_header + + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } else if (!strcasecmp(content_type, "image/x-rgb")) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } else if (!strcasecmp(content_type, "image/x-rgbz")) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } else if (!strcasecmp(content_type, "multipart/x-mixed-replace")) { + // Image stream, so start processing + if (!content_boundary[0]) { + Error("No content boundary found in header '%s'", content_type_header); + return -1; + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ + //// MPEG stream, coming soon! + //} + else { + Error("Unrecognised content type '%s'", content_type); + return -1; } } else { Debug(3, "Unable to extract entire header from stream, continuing"); @@ -860,14 +827,13 @@ int RemoteCameraHttp::GetResponse() { break; } case SUBHEADER : - { n_subheaders = 0; boundary_header = 0; subcontent_length_header[0] = '\0'; subcontent_type_header[0] = '\0'; content_length = 0; content_type[0] = '\0'; - } + FALLTHROUGH; case SUBHEADERCONT : { char *crlf = nullptr; @@ -875,24 +841,27 @@ int RemoteCameraHttp::GetResponse() { int subheader_len = buffer.size(); bool all_headers = false; - while( true ) { - int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); - if ( n_subheaders ) { - if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { + while (!zm_terminate) { + int crlf_len = memspn(subheader_ptr, "\r\n", subheader_len); + if (n_subheaders) { + if ( (crlf_len == 2 && !strncmp(subheader_ptr, "\n\n", crlf_len)) + || + (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) + ) { *subheader_ptr = '\0'; subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + subheader_len -= buffer.consume(subheader_ptr-(char *)buffer); all_headers = true; break; } } - if ( crlf_len ) { - if ( subheader_len == crlf_len ) { + if (crlf_len) { + if (subheader_len == crlf_len) { break; } else { *subheader_ptr = '\0'; subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + subheader_len -= buffer.consume(subheader_ptr-(char *)buffer); } } @@ -936,29 +905,29 @@ int RemoteCameraHttp::GetResponse() { } } - if ( all_headers && boundary_header ) { + if (all_headers && boundary_header) { char *start_ptr/*, *end_ptr*/; - Debug( 3, "Got boundary '%s'", boundary_header ); + Debug(3, "Got boundary '%s'", boundary_header); - if ( subcontent_length_header[0] ) { - start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got subcontent length '%d'", content_length ); + if (subcontent_length_header[0]) { + start_ptr = subcontent_length_header + strspn(subcontent_length_header, " "); + content_length = atoi(start_ptr); + Debug(3, "Got subcontent length '%d'", content_length); } - if ( subcontent_type_header[0] ) { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); - Debug( 3, "Got subcontent type '%s'", content_type ); + if (subcontent_type_header[0]) { + //memset(content_type, 0, sizeof(content_type)); + start_ptr = subcontent_type_header + strspn(subcontent_type_header, " "); + strcpy(content_type, start_ptr); + Debug(3, "Got subcontent type '%s'", content_type); } state = CONTENT; } else { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to read subheader" ); - return( -1 ); + Debug(3, "Unable to extract subheader from stream, retrying"); + int buffer_len = GetData(); + if (buffer_len < 0) { + Error("Unable to read subheader"); + return -1; } bytes += buffer_len; state = SUBHEADERCONT; @@ -966,108 +935,108 @@ int RemoteCameraHttp::GetResponse() { break; } case CONTENT : { - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( content_type, ';' ); - if ( semicolon ) { + char * semicolon = strchr(content_type, ';'); + if (semicolon) { *semicolon = '\0'; } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + if (!strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg")) { format = JPEG; - } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + } else if (!strcasecmp(content_type, "image/x-rgb")) { format = X_RGB; - } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + } else if (!strcasecmp(content_type, "image/x-rgbz")) { format = X_RGBZ; } else { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); + Error("Found unsupported content type '%s'", content_type); + return -1; } // This is an early test for jpeg content, so we can bail early - if ( format == JPEG && buffer.size() >= 2 ) { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); + if (format == JPEG and buffer.size() >= 2) { + if (buffer[0] != 0xff or buffer[1] != 0xd8) { + Error("Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1]); + return -1; } } - if ( content_length ) { - while ( ( (long)buffer.size() < content_length ) && ! zm_terminate ) { + if (content_length) { + while (((long)buffer.size() < content_length) and !zm_terminate) { Debug(4, "getting more data"); - int bytes_read = GetData(); - if ( bytes_read < 0 ) { + int bytes_read = ReadData(buffer, content_length-buffer.size()); + if (bytes_read < 0) { Error("Unable to read content"); return -1; } bytes += bytes_read; } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + Debug(3, "Got end of image by length, content-length = %d", content_length); } else { // Read until we find the end of image or the stream closes. - while ( !content_length && !zm_terminate ) { + while (!content_length && !zm_terminate) { Debug(4, "!content_length, ReadData"); - buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); + int buffer_len = ReadData(buffer); + if (buffer_len < 0) { + Error("Unable to read content"); + return -1; } bytes += buffer_len; int buffer_size = buffer.size(); - if ( buffer_len ) { + if (buffer_len) { // Got some data - if ( mode == MULTI_IMAGE ) { + if (mode == MULTI_IMAGE) { // Look for the boundary marker, determine content length using it's position - if ( char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size ) ) { + if (char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size)) { content_length = start_ptr - (char *)buffer; - Debug( 2, "Got end of image by pattern (crlf--), content-length = %d", content_length ); + Debug(2, "Got end of image by pattern (crlf--), content-length = %d", content_length); } else { - Debug( 2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length ); + Debug(2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length); } } // end if MULTI_IMAGE } else { content_length = buffer_size; - Debug( 2, "Got end of image by closure, content-length = %d", content_length ); - if ( mode == SINGLE_IMAGE ) { + Debug(2, "Got end of image by closure, content-length = %d", content_length); + if (mode == SINGLE_IMAGE) { char *end_ptr = (char *)buffer+buffer_size; // strip off any last line feeds - while( *end_ptr == '\r' || *end_ptr == '\n' ) { + while (*end_ptr == '\r' || *end_ptr == '\n') { content_length--; end_ptr--; } - if ( end_ptr != ((char *)buffer+buffer_size) ) { - Debug( 2, "Trimmed end of image, new content-length = %d", content_length ); + if (end_ptr != ((char *)buffer+buffer_size)) { + Debug(2, "Trimmed end of image, new content-length = %d", content_length); } } // end if SINGLE_IMAGE } // end if read some data } // end while ! content_length } // end if content_length - if ( mode == SINGLE_IMAGE ) { + if (mode == SINGLE_IMAGE) { state = HEADER; Disconnect(); } else { state = SUBHEADER; } - if ( format == JPEG && buffer.size() >= 2 ) { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); + if (format == JPEG && buffer.size() >= 2) { + if (buffer[0] != 0xff || buffer[1] != 0xd8) { + Error("Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1]); + return -1; } } - Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } // end cast CONTENT + Debug(3, "Returning %d bytes, buffer size: (%d) bytes of captured content", + content_length, buffer.size()); + return content_length; + } // end case CONTENT } // end switch } } - return( 0 ); -} + return 0; +} // end RemoteCameraHttp::GetResponse int RemoteCameraHttp::PrimeCapture() { if ( sd < 0 ) { @@ -1078,7 +1047,8 @@ int RemoteCameraHttp::PrimeCapture() { mode = SINGLE_IMAGE; buffer.clear(); } - return 0; + getVideoStream(); + return 1; } int RemoteCameraHttp::PreCapture() { @@ -1093,64 +1063,62 @@ int RemoteCameraHttp::PreCapture() { if ( mode == SINGLE_IMAGE ) { if ( SendRequest() < 0 ) { Error("Unable to send request"); - Disconnect(); return -1; } } - return 0; -} + return 1; +} // end int RemoteCameraHttp::PreCapture() -int RemoteCameraHttp::Capture( Image &image ) { +int RemoteCameraHttp::Capture(ZMPacket &packet) { int content_length = GetResponse(); - if ( content_length == 0 ) { - Warning( "Unable to capture image, retrying" ); + if (content_length == 0) { + Warning("Unable to capture image, retrying"); return 0; } - if ( content_length < 0 ) { - Error( "Unable to get response, disconnecting" ); - Disconnect(); + if (content_length < 0) { + Error("Unable to get response, disconnecting"); return -1; } - switch( format ) { + + if (!packet.image) { + Debug(4, "Allocating image"); + packet.image = new Image(width, height, colours, subpixelorder); + } + Image *image = packet.image; + packet.keyframe = 1; + packet.codec_type = AVMEDIA_TYPE_VIDEO; + packet.packet.stream_index = mVideoStreamId; + packet.stream = mVideoStream; + + switch (format) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return -1; - } - break; - } - case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return -1; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); - Disconnect(); - return -1; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); - Disconnect(); + if (!image->DecodeJpeg(buffer.extract(content_length), content_length, colours, subpixelorder)) { + Error("Unable to decode jpeg"); return -1; } + break; + case X_RGB : + if (content_length != (long)image->Size()) { + Error("Image length mismatch, expected %d bytes, content length was %d", + image->Size(), content_length); + return -1; + } + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); + break; + case X_RGBZ : + if (!image->Unzip(buffer.extract(content_length), content_length)) { + Error("Unable to unzip RGB image"); + return -1; + } + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); + break; + default : + Error("Unexpected image format encountered"); + return -1; } return 1; -} +} // end ZmPacket *RmoteCameraHttp::Capture( &image ); int RemoteCameraHttp::PostCapture() { - return 0; + return 1; } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index d9154608c..dc793459b 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -20,11 +20,8 @@ #ifndef ZM_REMOTE_CAMERA_HTTP_H #define ZM_REMOTE_CAMERA_HTTP_H -#include "zm_remote_camera.h" - #include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" +#include "zm_remote_camera.h" // // Class representing 'http' cameras, i.e. those which are @@ -44,23 +41,37 @@ protected: enum { SIMPLE, REGEXP } method; public: - RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + RemoteCameraHttp( + const Monitor *monitor, + const std::string &method, + const std::string &host, + const std::string &port, + const std::string &path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); ~RemoteCameraHttp(); - void Initialise(); - void Terminate() { Disconnect(); } - int Connect(); - int Disconnect(); + void Initialise() override; + void Terminate() override { Disconnect(); } + int Connect() override; + int Disconnect() override; int SendRequest(); int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetData(); int GetResponse(); - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; - int Close() { Disconnect(); return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture( ZMPacket &p ) override; + int PostCapture() override; + int Close() override { Disconnect(); return 0; }; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_nvsocket.cpp b/src/zm_remote_camera_nvsocket.cpp index 9045b962a..f66d31fcb 100644 --- a/src/zm_remote_camera_nvsocket.cpp +++ b/src/zm_remote_camera_nvsocket.cpp @@ -19,12 +19,12 @@ #include "zm_remote_camera_nvsocket.h" -#include "zm_mem_utils.h" - -#include -#include -#include +#include "zm_monitor.h" +#include "zm_packet.h" #include +#include +#include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -34,7 +34,7 @@ #endif RemoteCameraNVSocket::RemoteCameraNVSocket( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_host, const std::string &p_port, const std::string &p_path, @@ -48,7 +48,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket( bool p_capture, bool p_record_audio ) : RemoteCamera( - p_monitor_id, + monitor, "http", p_host, p_port, @@ -68,6 +68,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket( timeout.tv_sec = 0; timeout.tv_usec = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; + mVideoStream = NULL; if ( capture ) { Initialise(); @@ -116,7 +117,7 @@ int RemoteCameraNVSocket::Connect() { close(sd); sd = -1; - Warning("Can't connect to socket mid: %d : %s", monitor_id, strerror(errno) ); + Warning("Can't connect to socket mid: %d : %s", monitor->Id(), strerror(errno)); return -1; } @@ -137,13 +138,13 @@ int RemoteCameraNVSocket::Disconnect() { } int RemoteCameraNVSocket::SendRequest( std::string request ) { - Debug( 4, "Sending request: %s", request.c_str() ); + //Debug( 4, "Sending request: %s", request.c_str() ); if ( write( sd, request.data(), request.length() ) < 0 ) { Error( "Can't write: %s", strerror(errno) ); Disconnect(); return( -1 ); } - Debug( 4, "Request sent" ); + //Debug( 4, "Request sent" ); return( 0 ); } @@ -178,11 +179,12 @@ int RemoteCameraNVSocket::PrimeCapture() { Disconnect(); return -1; } + mVideoStreamId=0; return 0; } -int RemoteCameraNVSocket::Capture( Image &image ) { +int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { if ( SendRequest("GetNextImage\n") < 0 ) { Warning( "Unable to capture image, retrying" ); return 0; @@ -202,7 +204,8 @@ int RemoteCameraNVSocket::Capture( Image &image ) { return 0; } - image.Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet.keyframe = 1; return 1; } diff --git a/src/zm_remote_camera_nvsocket.h b/src/zm_remote_camera_nvsocket.h index fbd7fcd86..1cd740af2 100644 --- a/src/zm_remote_camera_nvsocket.h +++ b/src/zm_remote_camera_nvsocket.h @@ -20,28 +20,19 @@ #ifndef ZM_REMOTE_CAMERA_NVSOCKET_H #define ZM_REMOTE_CAMERA_NVSOCKET_H +#include "zm_buffer.h" #include "zm_remote_camera.h" -#include "zm_buffer.h" -#include "zm_regexp.h" -#include "zm_utils.h" - -// -// Class representing 'http' cameras, i.e. those which are -// accessed over a network connection using http -// class RemoteCameraNVSocket : public RemoteCamera { protected: std::string request; struct timeval timeout; - //struct hostent *hp; - //struct sockaddr_in sa; int sd; Buffer buffer; public: RemoteCameraNVSocket( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &host, const std::string &port, const std::string &path, @@ -56,18 +47,16 @@ public: bool p_record_audio ); ~RemoteCameraNVSocket(); - void Initialise(); - void Terminate() { Disconnect(); } - int Connect(); - int Disconnect(); - int SendRequest( std::string ); - int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); + void Initialise() override; + void Terminate() override { Disconnect(); } + int Connect() override; + int Disconnect() override; + int SendRequest(std::string); int GetResponse(); - int PrimeCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; - int Close() { return 0; }; + int PrimeCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; + int Close() override { return 0; }; }; #endif // ZM_REMOTE_CAMERA_NVSOCKET_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 98b0e3030..78c947faa 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -17,19 +17,16 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" +#include "zm_remote_camera_rtsp.h" + +#include "zm_config.h" +#include "zm_monitor.h" +#include "zm_packet.h" #if HAVE_LIBAVFORMAT -#include "zm_remote_camera_rtsp.h" -#include "zm_ffmpeg.h" -#include "zm_mem_utils.h" - -#include -#include - RemoteCameraRtsp::RemoteCameraRtsp( - unsigned int p_monitor_id, + const Monitor *monitor, const std::string &p_method, const std::string &p_host, const std::string &p_port, @@ -44,10 +41,14 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_colour, bool p_capture, bool p_record_audio ) : - RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), - rtsp_describe( p_rtsp_describe ), - rtspThread( 0 ) - + RemoteCamera( + monitor, "rtsp", + p_host, p_port, p_path, + p_width, p_height, p_colours, + p_brightness, p_contrast, p_hue, p_colour, + p_capture, p_record_audio), + rtsp_describe(p_rtsp_describe), + frameCount(0) { if ( p_method == "rtpUni" ) method = RtspThread::RTP_UNICAST; @@ -58,25 +59,12 @@ RemoteCameraRtsp::RemoteCameraRtsp( else if ( p_method == "rtpRtspHttp" ) method = RtspThread::RTP_RTSP_HTTP; else - Fatal("Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor_id); + Fatal("Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor->Id()); if ( capture ) { Initialise(); } - mFormatContext = nullptr; - mVideoStreamId = -1; - mAudioStreamId = -1; - mCodecContext = nullptr; - mCodec = nullptr; - mRawFrame = nullptr; - mFrame = nullptr; - frameCount = 0; - startTime=0; - -#if HAVE_LIBSWSCALE - mConvertContext = nullptr; -#endif /* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ if ( colours == ZM_COLOUR_RGB32 ) { subpixelorder = ZM_SUBPIX_ORDER_RGBA; @@ -93,20 +81,13 @@ RemoteCameraRtsp::RemoteCameraRtsp( } // end RemoteCameraRtsp::RemoteCameraRtsp(...) RemoteCameraRtsp::~RemoteCameraRtsp() { - av_frame_free(&mFrame); - av_frame_free(&mRawFrame); - -#if HAVE_LIBSWSCALE - if ( mConvertContext ) { - sws_freeContext(mConvertContext); - mConvertContext = nullptr; - } -#endif - if ( mCodecContext ) { - avcodec_close(mCodecContext); - mCodecContext = nullptr; // Freed by avformat_free_context in the destructor of RtspThread class + if ( mVideoCodecContext ) { + avcodec_close(mVideoCodecContext); + mVideoCodecContext = nullptr; // Freed by avformat_free_context in the destructor of RtspThread class } + // Is allocated in RTSPThread and is free there as well + mFormatContext = nullptr; if ( capture ) { Terminate(); @@ -132,19 +113,15 @@ void RemoteCameraRtsp::Terminate() { } int RemoteCameraRtsp::Connect() { - rtspThread = new RtspThread(monitor_id, method, protocol, host, port, path, auth, rtsp_describe); - - rtspThread->start(); + rtspThread = ZM::make_unique(monitor->Id(), method, protocol, host, port, path, auth, rtsp_describe); return 0; } int RemoteCameraRtsp::Disconnect() { - if ( rtspThread ) { - rtspThread->stop(); - rtspThread->join(); - delete rtspThread; - rtspThread = nullptr; + if (rtspThread) { + rtspThread->Stop(); + rtspThread.reset(); } return 0; } @@ -166,22 +143,27 @@ int RemoteCameraRtsp::PrimeCapture() { // Find first video stream present mVideoStreamId = -1; mAudioStreamId = -1; - + // Find the first video stream. for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { if ( is_video_stream(mFormatContext->streams[i]) ) { if ( mVideoStreamId == -1 ) { mVideoStreamId = i; + mVideoStream = mFormatContext->streams[i]; + mVideoStream->time_base = AV_TIME_BASE_Q; continue; } else { Debug(2, "Have another video stream."); } +#if 0 } else if ( is_audio_stream(mFormatContext->streams[i]) ) { if ( mAudioStreamId == -1 ) { mAudioStreamId = i; + mAudioStream = mFormatContext->streams[i]; } else { Debug(2, "Have another audio stream."); } +#endif } else { Debug(1, "Have unknown codec type in stream %d", i); } @@ -193,30 +175,26 @@ int RemoteCameraRtsp::PrimeCapture() { Debug(3, "Unable to locate audio stream"); // Get a pointer to the codec context for the video stream - mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mVideoCodecContext = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); +#else + mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; +#endif // Find the decoder for the video stream - mCodec = avcodec_find_decoder(mCodecContext->codec_id); - if ( mCodec == nullptr ) - Panic("Unable to locate codec %d decoder", mCodecContext->codec_id); + AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); + if ( codec == nullptr ) + Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); // Open codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mCodecContext, mCodec) < 0 ) + if ( avcodec_open(mVideoCodecContext, codec) < 0 ) #else - if ( avcodec_open2(mCodecContext, mCodec, 0) < 0 ) + if ( avcodec_open2(mVideoCodecContext, codec, 0) < 0 ) #endif Panic("Can't open codec"); - // Allocate space for the native video frame - mRawFrame = zm_av_frame_alloc(); - - // Allocate space for the converted video frame - mFrame = zm_av_frame_alloc(); - - if ( mRawFrame == nullptr || mFrame == nullptr ) - Fatal("Unable to allocate frame(s)"); - #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); #else @@ -224,51 +202,34 @@ int RemoteCameraRtsp::PrimeCapture() { #endif if ( (unsigned int)pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %d", pSize, imagesize); - } -/* -#if HAVE_LIBSWSCALE - if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { - Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff)); + Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } - if(!sws_isSupportedOutput(imagePixFormat)) { - Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); - } - -#else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); -#endif // HAVE_LIBSWSCALE -*/ - - return 0; -} + return 1; +} // end PrimeCapture int RemoteCameraRtsp::PreCapture() { - if ( !rtspThread->isRunning() ) + if (!rtspThread || rtspThread->IsStopped()) return -1; if ( !rtspThread->hasSources() ) { Error("Cannot precapture, no RTP sources"); return -1; } - return 0; + return 1; } -int RemoteCameraRtsp::Capture( Image &image ) { - AVPacket packet; - uint8_t* directbuffer; +int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { int frameComplete = false; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == nullptr ) { - Error("Failed requesting writeable buffer for the captured image."); - return -1; + AVPacket *packet = &zm_packet.packet; + if ( !zm_packet.image ) { + Debug(1, "Allocating image %dx%d %d colours %d", width, height, colours, subpixelorder); + zm_packet.image = new Image(width, height, colours, subpixelorder); } + - while ( true ) { + while ( !frameComplete ) { buffer.clear(); - if ( !rtspThread->isRunning() ) + if (!rtspThread || rtspThread->IsStopped()) return -1; if ( rtspThread->getFrame(buffer) ) { @@ -278,12 +239,13 @@ int RemoteCameraRtsp::Capture( Image &image ) { if ( !buffer.size() ) return -1; - if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { + if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { // SPS and PPS frames should be saved and appended to IDR frames int nalType = (buffer.head()[3] & 0x1f); // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures - if ( nalType == 7 ) { + if ( nalType == 1 ) { + } else if ( nalType == 7 ) { lastSps = buffer; continue; } else if ( nalType == 8 ) { @@ -291,6 +253,8 @@ int RemoteCameraRtsp::Capture( Image &image ) { lastPps = buffer; continue; } else if ( nalType == 5 ) { + packet->flags |= AV_PKT_FLAG_KEY; + zm_packet.keyframe = 1; // IDR buffer += lastSps; buffer += lastPps; @@ -301,92 +265,59 @@ int RemoteCameraRtsp::Capture( Image &image ) { Debug(3, "Not an h264 packet"); } - av_init_packet(&packet); - while ( (!frameComplete) && (buffer.size() > 0) ) { - packet.data = buffer.head(); - packet.size = buffer.size(); - bytes += packet.size; + //while ( (!frameComplete) && (buffer.size() > 0) ) { + if ( buffer.size() > 0 ) { + packet->data = buffer.head(); + packet->size = buffer.size(); + bytes += packet->size; - // So I think this is the magic decode step. Result is a raw image? - int len = zm_send_packet_receive_frame(mCodecContext, mRawFrame, packet); - if ( len < 0 ) { + struct timeval now; + gettimeofday(&now, NULL); + packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; + + int bytes_consumed = zm_packet.decode(mVideoCodecContext); + if ( bytes_consumed < 0 ) { Error("Error while decoding frame %d", frameCount); - Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); - buffer.clear(); - continue; - } - frameComplete = true; - Debug(2, "Frame: %d - %d/%d", frameCount, len, buffer.size()); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - - buffer -= len; - } - // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? - if ( frameComplete ) { - - Debug(3, "Got frame %d", frameCount); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. - int size = av_image_fill_arrays( - mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, - (AV_PIX_FMT_RGBA == imagePixFormat ? 32 : 1) - ); - if ( size < 0 ) { - Error("Problem setting up data pointers into image %s", - av_make_error_string(size).c_str()); + //Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); } + buffer -= packet->size; + if ( bytes_consumed ) { + zm_dump_video_frame(zm_packet.in_frame, "remote_rtsp_decode"); + if ( ! mVideoStream-> +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + codecpar #else - avpicture_fill((AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); + codec +#endif + ->width ) { + zm_dump_codec(mVideoCodecContext); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + zm_dump_codecpar(mVideoStream->codecpar); + mVideoStream->codecpar->width = zm_packet.in_frame->width; + mVideoStream->codecpar->height = zm_packet.in_frame->height; +#else + mVideoStream->codec->width = zm_packet.in_frame->width; + mVideoStream->codec->height = zm_packet.in_frame->height; +#endif +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + zm_dump_codecpar(mVideoStream->codecpar); #endif - - #if HAVE_LIBSWSCALE - if ( mConvertContext == nullptr ) { - mConvertContext = sws_getContext( - mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, - width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); - - if ( mConvertContext == nullptr ) - Fatal("Unable to create conversion context"); - - if ( - ((unsigned int)mRawFrame->width != width) - || - ((unsigned int)mRawFrame->height != height) - ) { - Warning("Monitor dimensions are %dx%d but camera is sending %dx%d", - width, height, mRawFrame->width, mRawFrame->height); } + zm_packet.codec_type = mVideoCodecContext->codec_type; + zm_packet.stream = mVideoStream; + frameComplete = true; + Debug(2, "Frame: %d - %d/%d", frameCount, bytes_consumed, buffer.size()); + packet->data = nullptr; + packet->size = 0; } - - if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize) < 0 ) - Fatal("Unable to convert raw format %u to target format %u at frame %d", - mCodecContext->pix_fmt, imagePixFormat, frameCount ); - #else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use RTSP cameras"); - #endif // HAVE_LIBSWSCALE - - frameCount++; - - } /* frame complete */ - - zm_av_packet_unref(&packet); + } } /* getFrame() */ - - if ( frameComplete ) - return 1; - } // end while true - // can never get here. - return 0; -} - -//Function to handle capture and store + return 1; +} // end int RemoteCameraRtsp::Capture(ZMPacket &packet) int RemoteCameraRtsp::PostCapture() { - return 0; + return 1; } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index be7542a84..c4734ebee 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -20,14 +20,9 @@ #ifndef ZM_REMOTE_CAMERA_RTSP_H #define ZM_REMOTE_CAMERA_RTSP_H -#include "zm_remote_camera.h" - -#include "zm_buffer.h" -#include "zm_utils.h" -#include "zm_rtsp.h" #include "zm_ffmpeg.h" -#include "zm_videostore.h" -#include "zm_packetqueue.h" +#include "zm_remote_camera.h" +#include "zm_rtsp.h" // // Class representing 'rtsp' cameras, i.e. those which are @@ -49,44 +44,57 @@ protected: RtspThread::RtspMethod method; - RtspThread *rtspThread; + std::unique_ptr rtspThread; int frameCount; #if HAVE_LIBAVFORMAT AVFormatContext *mFormatContext; - int mVideoStreamId; - int mAudioStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; _AVPIXELFORMAT imagePixFormat; #endif // HAVE_LIBAVFORMAT - bool wasRecording; - VideoStore *videoStore; - char oldDirectory[4096]; - int64_t startTime; - -#if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext; -#endif public: - RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + RemoteCameraRtsp( + const Monitor *monitor, + const std::string &method, + const std::string &host, + const std::string &port, + const std::string &path, + int p_width, + int p_height, + bool p_rtsp_describe, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio); ~RemoteCameraRtsp(); - void Initialise(); - void Terminate(); - int Connect(); - int Disconnect(); + void Initialise() override; + void Terminate() override; + int Connect() override; + int Disconnect() override; - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(ZMPacket &p) override; + int PostCapture() override; + int Close() override { return 0; }; + + AVStream *get_VideoStream() { + if ( mVideoStreamId != -1 ) + return mFormatContext->streams[mVideoStreamId]; + return nullptr; + } + AVStream *get_AudioStream() { + if ( mAudioStreamId != -1 ) + return mFormatContext->streams[mAudioStreamId]; + return nullptr; + } + AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; }; + AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; }; }; #endif // ZM_REMOTE_CAMERA_RTSP_H diff --git a/src/zm_rgb.h b/src/zm_rgb.h index 1b6cb7550..58ad9bfae 100644 --- a/src/zm_rgb.h +++ b/src/zm_rgb.h @@ -20,26 +20,28 @@ #ifndef ZM_RGB_H #define ZM_RGB_H -typedef uint32_t Rgb; // RGB colour type +#include "zm_define.h" -#define WHITE 0xff -#define WHITE_R 0xff -#define WHITE_G 0xff -#define WHITE_B 0xff +typedef uint32 Rgb; // RGB colour type -#define BLACK 0x00 -#define BLACK_R 0x00 -#define BLACK_G 0x00 -#define BLACK_B 0x00 +constexpr uint8 kWhite = 0xff; +constexpr uint8 kWhiteR = 0xff; +constexpr uint8 kWhiteG = 0xff; +constexpr uint8 kWhiteB = 0xff; -#define RGB_WHITE (0x00ffffff) -#define RGB_BLACK (0x00000000) -#define RGB_RED (0x000000ff) -#define RGB_GREEN (0x0000ff00) -#define RGB_BLUE (0x00ff0000) -#define RGB_ORANGE (0x0000a5ff) -#define RGB_PURPLE (0x00800080) -#define RGB_TRANSPARENT (0x01000000) +constexpr uint8 kBlack = 0x00; +constexpr uint8 kBlackR = 0x00; +constexpr uint8 kBlackG = 0x00; +constexpr uint8 kBlackB = 0x00; + +constexpr Rgb kRGBWhite = 0x00ffffff; +constexpr Rgb kRGBBlack = 0x00000000; +constexpr Rgb kRGBRed = 0x000000ff; +constexpr Rgb kRGBGreen = 0x0000ff00; +constexpr Rgb kRGBBlue = 0x00ff0000; +constexpr Rgb kRGBOrange = 0x0000a5ff; +constexpr Rgb kRGBPurple = 0x00800080; +constexpr Rgb kRGBTransparent = 0x01000000; #define RGB_VAL(v,c) (((v)>>(16-((c)*8)))&0xff) diff --git a/src/zm_rtp.h b/src/zm_rtp.h index 25403f1a5..f45e14c74 100644 --- a/src/zm_rtp.h +++ b/src/zm_rtp.h @@ -20,8 +20,6 @@ #ifndef ZM_RTP_H #define ZM_RTP_H -#include "zm.h" - #define RTP_VERSION 2 #endif // ZM_RTP_H diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index 2ffc0c404..9d74359fc 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -15,22 +15,26 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#include "zm.h" - -#if HAVE_LIBAVFORMAT +// #include "zm_rtp_ctrl.h" -#include "zm_time.h" +#include "zm_config.h" +#include "zm_rtp.h" #include "zm_rtsp.h" -#include +#if HAVE_LIBAVFORMAT -RtpCtrlThread::RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource ) - : mRtspThread( rtspThread ), mRtpSource( rtpSource ), mStop( false ) +RtpCtrlThread::RtpCtrlThread(RtspThread &rtspThread, RtpSource &rtpSource) + : mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { + mThread = std::thread(&RtpCtrlThread::Run, this); +} + +RtpCtrlThread::~RtpCtrlThread() { + Stop(); + if (mThread.joinable()) + mThread.join(); } int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen ) { @@ -124,7 +128,7 @@ int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen ) } case RTCP_BYE : Debug(5, "RTCP Got BYE"); - mStop = true; + Stop(); break; case RTCP_APP : // Ignoring as per RFC 3550 @@ -244,12 +248,12 @@ int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes ) { return nBytes; } -int RtpCtrlThread::run() { +void RtpCtrlThread::Run() { Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() ); - SockAddrInet localAddr, remoteAddr; + ZM::SockAddrInet localAddr, remoteAddr; bool sendReports; - UdpInetSocket rtpCtrlServer; + ZM::UdpInetSocket rtpCtrlServer; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) ) Fatal( "Failed to bind RTCP server" ); @@ -267,7 +271,7 @@ int RtpCtrlThread::run() { // The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets. // Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response - Select select( 10 ); + ZM::Select select( 10 ); select.addReader( &rtpCtrlServer ); unsigned char buffer[ZM_NETWORK_BUFSIZ]; @@ -275,10 +279,9 @@ int RtpCtrlThread::run() { time_t last_receive = time(nullptr); bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true. - while ( !mStop && select.wait() >= 0 ) { - + while (!mTerminate && select.wait() >= 0) { time_t now = time(nullptr); - Select::CommsList readable = select.getReadable(); + ZM::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { if ( ! timeout ) { // With this code here, we will send an SDES and RR packet every 10 seconds @@ -286,15 +289,14 @@ int RtpCtrlThread::run() { unsigned char *bufferPtr = buffer; bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) ); - Debug( 3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %d", - bufferPtr-buffer, rtpCtrlServer.getWriteDesc(), ( now-last_receive) ); + Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %" PRIi64, + bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), static_cast(now - last_receive)); if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 ) Error("Unable to send: %s", strerror(errno)); timeout = true; continue; } else { - //Error( "RTCP timed out" ); - Debug(1, "RTCP timed out. Time since last receive: %d", ( now-last_receive) ); + Debug(1, "RTCP timed out. Time since last receive: %" PRIi64, static_cast(now - last_receive)); continue; //break; } @@ -302,8 +304,8 @@ int RtpCtrlThread::run() { timeout = false; last_receive = time(nullptr); } - for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( UdpInetSocket *socket = dynamic_cast(*iter) ) { + for ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { + if ( ZM::UdpInetSocket *socket = dynamic_cast(*iter) ) { ssize_t nBytes = socket->recv( buffer, sizeof(buffer) ); Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() ); @@ -321,7 +323,7 @@ int RtpCtrlThread::run() { } } else { // Here is another case of not receiving some data causing us to terminate... why? Sometimes there are pauses in the interwebs. - mStop = true; + Stop(); break; } } else { @@ -330,8 +332,7 @@ int RtpCtrlThread::run() { } // end foeach comms iterator } rtpCtrlServer.close(); - mRtspThread.stop(); - return 0; + mRtspThread.Stop(); } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtp_ctrl.h b/src/zm_rtp_ctrl.h index 9e5306f92..9346496f7 100644 --- a/src/zm_rtp_ctrl.h +++ b/src/zm_rtp_ctrl.h @@ -20,9 +20,9 @@ #ifndef ZM_RTP_CTRL_H #define ZM_RTP_CTRL_H -#include "zm_rtp.h" -#include "zm_comms.h" -#include "zm_thread.h" +#include +#include +#include // Defined in ffmpeg rtp.h //#define RTP_MAX_SDES 255 // maximum text length for SDES @@ -34,7 +34,7 @@ class RtspThread; class RtpSource; -class RtpCtrlThread : public Thread { +class RtpCtrlThread { friend class RtspThread; private: @@ -123,7 +123,9 @@ private: RtspThread &mRtspThread; RtpSource &mRtpSource; int mPort; - bool mStop; + + std::atomic mTerminate; + std::thread mThread; private: int recvPacket( const unsigned char *packet, ssize_t packetLen ); @@ -131,14 +133,13 @@ private: int generateSdes( const unsigned char *packet, ssize_t packetLen ); int generateBye( const unsigned char *packet, ssize_t packetLen ); int recvPackets( unsigned char *buffer, ssize_t nBytes ); - int run(); + void Run(); public: RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource ); + ~RtpCtrlThread(); - void stop() { - mStop = true; - } + void Stop() { mTerminate = true; } }; #endif // ZM_RTP_CTRL_H diff --git a/src/zm_rtp_data.cpp b/src/zm_rtp_data.cpp index de7c732d3..aed02aea6 100644 --- a/src/zm_rtp_data.cpp +++ b/src/zm_rtp_data.cpp @@ -17,19 +17,24 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" +#include "zm_rtp_data.h" + +#include "zm_config.h" +#include "zm_rtsp.h" +#include "zm_signal.h" #if HAVE_LIBAVFORMAT -#include "zm_rtp_data.h" - -#include "zm_rtsp.h" - -#include - RtpDataThread::RtpDataThread(RtspThread &rtspThread, RtpSource &rtpSource) : - mRtspThread(rtspThread), mRtpSource(rtpSource), mStop(false) + mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) { + mThread = std::thread(&RtpDataThread::Run, this); +} + +RtpDataThread::~RtpDataThread() { + Stop(); + if (mThread.joinable()) + mThread.join(); } bool RtpDataThread::recvPacket(const unsigned char *packet, size_t packetLen) { @@ -56,12 +61,12 @@ bool RtpDataThread::recvPacket(const unsigned char *packet, size_t packetLen) { return mRtpSource.handlePacket(packet, packetLen); } -int RtpDataThread::run() { +void RtpDataThread::Run() { Debug(2, "Starting data thread %d on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalDataPort()); - SockAddrInet localAddr; - UdpInetServer rtpDataSocket; + ZM::SockAddrInet localAddr; + ZM::UdpInetServer rtpDataSocket; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpDataSocket.bind(mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()) ) Fatal("Failed to bind RTP server"); @@ -73,25 +78,25 @@ int RtpDataThread::run() { } Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()); - Select select(3); + ZM::Select select(3); select.addReader(&rtpDataSocket); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - while ( !zm_terminate && !mStop && (select.wait() >= 0) ) { - Select::CommsList readable = select.getReadable(); + while ( !zm_terminate && !mTerminate && (select.wait() >= 0) ) { + ZM::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { Error("RTP timed out"); - mStop = true; + Stop(); break; } - for ( Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( UdpInetServer *socket = dynamic_cast(*iter) ) { + for ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { + if ( ZM::UdpInetServer *socket = dynamic_cast(*iter) ) { int nBytes = socket->recv(buffer, sizeof(buffer)); Debug(4, "Got %d bytes on sd %d", nBytes, socket->getReadDesc()); if ( nBytes ) { recvPacket(buffer, nBytes); } else { - mStop = true; + Stop(); break; } } else { @@ -100,8 +105,7 @@ int RtpDataThread::run() { } // end foreach commsList } rtpDataSocket.close(); - mRtspThread.stop(); - return 0; + mRtspThread.Stop(); } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtp_data.h b/src/zm_rtp_data.h index dae449ea5..e20878212 100644 --- a/src/zm_rtp_data.h +++ b/src/zm_rtp_data.h @@ -20,10 +20,9 @@ #ifndef ZM_RTP_DATA_H #define ZM_RTP_DATA_H -#include "zm_thread.h" -#include "zm_buffer.h" - -#include +#include "zm_define.h" +#include +#include class RtspThread; class RtpSource; @@ -42,26 +41,26 @@ struct RtpDataHeader uint32_t csrc[]; // optional CSRC list }; -class RtpDataThread : public Thread +class RtpDataThread { friend class RtspThread; private: RtspThread &mRtspThread; RtpSource &mRtpSource; - bool mStop; + + std::atomic mTerminate; + std::thread mThread; private: bool recvPacket( const unsigned char *packet, size_t packetLen ); - int run(); + void Run(); public: RtpDataThread( RtspThread &rtspThread, RtpSource &rtpSource ); + ~RtpDataThread(); - void stop() - { - mStop = true; - } + void Stop() { mTerminate = true; } }; #endif // ZM_RTP_DATA_H diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 0ac963947..ff69a6069 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -21,8 +21,9 @@ #include "zm_time.h" #include "zm_rtp_data.h" - +#include "zm_utils.h" #include +#include #if HAVE_LIBAVCODEC @@ -79,6 +80,12 @@ RtpSource::RtpSource( Warning("The device is using a codec (%d) that may not be supported. Do not be surprised if things don't work.", mCodecId); } +RtpSource::~RtpSource() { + mTerminate = true; + mFrameReadyCv.notify_all(); + mFrameProcessedCv.notify_all(); +} + void RtpSource::init(uint16_t seq) { Debug(3, "Initialising sequence"); mBaseSeq = seq; @@ -158,14 +165,12 @@ void RtpSource::updateJitter( const RtpDataHeader *header ) { uint32_t localTimeRtp = mBaseTimeRtp + uint32_t(tvDiffSec(mBaseTimeReal) * mRtpFactor); uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN); - Debug(5, "Delta rtp = %.6f\n" - "Local RTP time = %x", - "Packet RTP time = %x", - "Packet transit RTP time = %x", - tvDiffSec(mBaseTimeReal), - localTimeRtp, - ntohl(header->timestampN), - packetTransit); + Debug(5, + "Delta rtp = %.6f\n Local RTP time = %x Packet RTP time = %x Packet transit RTP time = %x", + tvDiffSec(mBaseTimeReal), + localTimeRtp, + ntohl(header->timestampN), + packetTransit); if ( mTransit > 0 ) { // Jitter @@ -232,19 +237,13 @@ void RtpSource::updateRtcpStats() { mLostFraction = (lostInterval << 8) / expectedInterval; Debug(5, - "Expected packets = %d\n", - "Lost packets = %d\n", - "Expected interval = %d\n", - "Received interval = %d\n", - "Lost interval = %d\n", - "Lost fraction = %d\n", - - mExpectedPackets, - mLostPackets, - expectedInterval, - receivedInterval, - lostInterval, - mLostFraction); + "Expected packets = %d\n Lost packets = %d\n Expected interval = %d\n Received interval = %d\n Lost interval = %d\n Lost fraction = %d\n", + mExpectedPackets, + mLostPackets, + expectedInterval, + receivedInterval, + lostInterval, + mLostFraction); } bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { @@ -292,7 +291,7 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { extraHeader = 2; break; - default: + default: Debug(3, "Unhandled nalType %d", nalType); } @@ -301,7 +300,7 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { mFrame.append("\x0\x0\x1", 3); } // end if H264 mFrame.append(packet+rtpHeaderSize+extraHeader, - packetLen-rtpHeaderSize-extraHeader); + packetLen-rtpHeaderSize-extraHeader); } else { Debug(3, "NOT H264 frame: type is %d", mCodecId); } @@ -312,16 +311,21 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { if ( mFrameGood ) { Debug(3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size()); - mFrameProcessed.setValueImmediate(false); - mFrameReady.updateValueSignal(true); - if ( !mFrameProcessed.getValueImmediate() ) { - // What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as - // if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false; - - for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ ) - if ( count > 1 ) - return false; + { + std::lock_guard lck(mFrameReadyMutex); + mFrameReady = true; } + mFrameReadyCv.notify_all(); + + { + std::unique_lock lck(mFrameProcessedMutex); + mFrameProcessedCv.wait(lck, [&]{ return mFrameProcessed || mTerminate; }); + mFrameProcessed = false; + } + + if (mTerminate) + return false; + mFrameCount++; } else { Warning("Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size()); @@ -349,16 +353,21 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { } bool RtpSource::getFrame(Buffer &buffer) { - if ( !mFrameReady.getValueImmediate() ) { - Debug(3, "Getting frame but not ready"); - // Allow for a couple of spurious returns - for ( int count = 0; !mFrameReady.getUpdatedValue(1); count++ ) - if ( count > 1 ) - return false; + { + std::unique_lock lck(mFrameReadyMutex); + mFrameReadyCv.wait(lck, [&]{ return mFrameReady || mTerminate; }); + mFrameReady = false; } + + if (mTerminate) + return false; + buffer = mFrame; - mFrameReady.setValueImmediate(false); - mFrameProcessed.updateValueSignal(true); + { + std::lock_guard lck(mFrameProcessedMutex); + mFrameProcessed = true; + } + mFrameProcessedCv.notify_all(); Debug(4, "Copied %d bytes", buffer.size()); return true; } diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index f91ba20f7..0d1bdee83 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -21,12 +21,13 @@ #define ZM_RTP_SOURCE_H #include "zm_buffer.h" +#include "zm_config.h" +#include "zm_define.h" #include "zm_ffmpeg.h" -#include "zm_thread.h" - -#include -#include +#include +#include #include +#include #if HAVE_LIBAVCODEC @@ -90,14 +91,23 @@ private: int mFrameCount; bool mFrameGood; bool prevM; - ThreadData mFrameReady; - ThreadData mFrameProcessed; + + bool mTerminate; + + bool mFrameReady; + std::condition_variable mFrameReadyCv; + std::mutex mFrameReadyMutex; + + bool mFrameProcessed; + std::condition_variable mFrameProcessedCv; + std::mutex mFrameProcessedMutex; private: void init(uint16_t seq); public: RtpSource( int id, const std::string &localHost, int localPortBase, const std::string &remoteHost, int remotePortBase, uint32_t ssrc, uint16_t seq, uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId ); + ~RtpSource(); bool updateSeq( uint16_t seq ); void updateJitter( const RtpDataHeader *header ); diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 53452bb94..4efe0a43b 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -17,20 +17,16 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" - -#if HAVE_LIBAVFORMAT - #include "zm_rtsp.h" +#include "zm_config.h" #include "zm_rtp_data.h" #include "zm_rtp_ctrl.h" #include "zm_db.h" -#include -#include -#include -#include +#include + +#if HAVE_LIBAVFORMAT int RtspThread::smMinDataPort = 0; int RtspThread::smMaxDataPort = 0; @@ -38,7 +34,7 @@ RtspThread::PortSet RtspThread::smAssignedPorts; bool RtspThread::sendCommand(std::string message) { if ( mNeedAuth ) { - StringVector parts = split(message, " "); + StringVector parts = Split(message, " "); if ( parts.size() > 1 ) message += mAuthenticator->getAuthHeader(parts[0], parts[1]); } @@ -46,7 +42,7 @@ bool RtspThread::sendCommand(std::string message) { message += stringtf("CSeq: %d\r\n\r\n", ++mSeq); Debug(2, "Sending RTSP message: %s", message.c_str()); if ( mMethod == RTP_RTSP_HTTP ) { - message = base64Encode(message); + message = Base64Encode(message); Debug(2, "Sending encoded RTSP message: %s", message.c_str()); if ( mRtspSocket2.send(message.c_str(), message.size()) != (int)message.length() ) { Error("Unable to send message '%s': %s", message.c_str(), strerror(errno)); @@ -74,7 +70,7 @@ bool RtspThread::recvResponse(std::string &response) { } else { Error("Response parse failure, %zd bytes follow", response.size()); if ( response.size() ) - Hexdump(Logger::ERROR, response.data(), min(response.size(),16)); + Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16)); } return false; } @@ -95,16 +91,9 @@ int RtspThread::requestPorts() { char sql[ZM_SQL_SML_BUFSIZ]; //FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors strncpy(sql, "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC", sizeof(sql)); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + MYSQL_RES *result = zmDbFetch(sql); + int nMonitors = mysql_num_rows(result); int position = 0; if ( nMonitors ) { @@ -165,7 +154,7 @@ RtspThread::RtspThread( mSsrc(0), mDist(UNDEFINED), mRtpTime(0), - mStop(false) + mTerminate(false) { mUrl = mProtocol+"://"+mHost+":"+mPort; if ( !mPath.empty() ) { @@ -183,15 +172,21 @@ RtspThread::RtspThread( mHttpSession = stringtf("%d", rand()); mNeedAuth = false; - StringVector parts = split(auth, ":"); - Debug(2, "# of auth parts %d", parts.size()); + StringVector parts = Split(auth, ":"); + Debug(2, "# of auth parts %zu", parts.size()); if ( parts.size() > 1 ) mAuthenticator = new zm::Authenticator(parts[0], parts[1]); else mAuthenticator = new zm::Authenticator(parts[0], ""); + + mThread = std::thread(&RtspThread::Run, this); } RtspThread::~RtspThread() { + Stop(); + if (mThread.joinable()) + mThread.join(); + if ( mFormatContext ) { #if LIBAVFORMAT_VERSION_CHECK(52, 96, 0, 96, 0) avformat_free_context(mFormatContext); @@ -208,7 +203,7 @@ RtspThread::~RtspThread() { mAuthenticator = nullptr; } -int RtspThread::run() { +void RtspThread::Run() { std::string message; std::string response; @@ -250,11 +245,11 @@ int RtspThread::run() { Debug(2, "Sending HTTP message: %s", message.c_str()); if ( mRtspSocket.send(message.c_str(), message.size()) != (int)message.length() ) { Error("Unable to send message '%s': %s", message.c_str(), strerror(errno)); - return -1; + return; } if ( mRtspSocket.recv(response) < 0 ) { Error("Recv failed; %s", strerror(errno)); - return -1; + return; } Debug(2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size()); @@ -266,9 +261,9 @@ int RtspThread::run() { } else { Error("Response parse failure, %zd bytes follow", response.size()); if ( response.size() ) - Hexdump(Logger::ERROR, response.data(), min(response.size(),16)); + Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16)); } - return -1; + return; } // If Server requests authentication, check WWW-Authenticate header and fill required fields // for requested authentication method @@ -286,7 +281,7 @@ int RtspThread::run() { if ( respCode != 200 ) { Error("Unexpected response code %d, text is '%s'", respCode, respText); - return -1; + return; } message = "POST "+mPath+" HTTP/1.0\r\n"; @@ -299,7 +294,7 @@ int RtspThread::run() { Debug(2, "Sending HTTP message: %s", message.c_str()); if ( mRtspSocket2.send(message.c_str(), message.size()) != (int)message.length() ) { Error("Unable to send message '%s': %s", message.c_str(), strerror(errno)); - return -1; + return; } } // end if ( mMethod == RTP_RTSP_HTTP ) @@ -309,23 +304,23 @@ int RtspThread::run() { // Request supported RTSP commands by the server message = "OPTIONS "+mUrl+" RTSP/1.0\r\n"; if ( !sendCommand(message) ) - return -1; + return; // A negative return here may indicate auth failure, but we will have setup the auth mechanisms so we need to retry. if ( !recvResponse(response) ) { if ( mNeedAuth ) { Debug(2, "Resending OPTIONS due to possible auth requirement"); if ( !sendCommand(message) ) - return -1; + return; if ( !recvResponse(response) ) - return -1; + return; } else { - return -1; + return; } } // end if failed response maybe due to auth char publicLine[256] = ""; - StringVector lines = split(response, "\r\n"); + StringVector lines = Split(response, "\r\n"); for ( size_t i = 0; i < lines.size(); i++ ) sscanf(lines[i].c_str(), "Public: %[^\r\n]\r\n", publicLine); @@ -351,17 +346,17 @@ int RtspThread::run() { const std::string endOfHeaders = "\r\n\r\n"; size_t sdpStart = response.find(endOfHeaders); if ( sdpStart == std::string::npos ) - return -1; + return; if ( mRtspDescribe ) { std::string DescHeader = response.substr(0, sdpStart); Debug(1, "Processing DESCRIBE response header '%s'", DescHeader.c_str()); - lines = split(DescHeader, "\r\n"); + lines = Split(DescHeader, "\r\n"); for ( size_t i = 0; i < lines.size(); i++ ) { // If the device sends us a url value for Content-Base in the response header, we should use that instead if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) ) { - mUrl = trimSpaces( lines[i].substr( 13 ) ); + mUrl = TrimSpaces(lines[i].substr(13)); Info("Received new Content-Base in DESCRIBE response header. Updated device Url to: '%s'", mUrl.c_str() ); break; } @@ -376,9 +371,9 @@ int RtspThread::run() { try { mSessDesc = new SessionDescriptor( mUrl, sdp ); mFormatContext = mSessDesc->generateFormatContext(); - } catch( const Exception &e ) { - Error( e.getMessage().c_str() ); - return -1; + } catch ( const Exception &e ) { + Error("%s", e.getMessage().c_str()); + return; } #if 0 @@ -402,8 +397,10 @@ int RtspThread::run() { if ( mFormatContext->nb_streams >= 1 ) { for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { - SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream( i ); -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream(i); +#if LIBAVFORMAT_VERSION_CHECK(57, 33, 0, 33, 0) + if ( mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) +#elif (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) #else if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) @@ -411,113 +408,108 @@ int RtspThread::run() { { // Check if control Url is absolute or relative controlUrl = mediaDesc->getControlUrl(); - if (std::equal(trackUrl.begin(), trackUrl.end(), controlUrl.begin())) { + if (trackUrl == controlUrl) { trackUrl = controlUrl; } else { - if ( *trackUrl.rbegin() != '/') { + if ( *trackUrl.rbegin() != '/' ) { trackUrl += "/" + controlUrl; } else { trackUrl += controlUrl; } } rtpClock = mediaDesc->getClock(); +#if LIBAVFORMAT_VERSION_CHECK(57, 33, 0, 33, 0) + codecId = mFormatContext->streams[i]->codecpar->codec_id; +#else codecId = mFormatContext->streams[i]->codec->codec_id; - // Hackery pokery - //rtpClock = mFormatContext->streams[i]->codec->sample_rate; +#endif break; - } - } - } + } // end if is video + } // end foreach stream + } // end if have stream - switch( mMethod ) { + switch ( mMethod ) { case RTP_UNICAST : - { localPorts[0] = requestPorts(); localPorts[1] = localPorts[0]+1; - message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="+stringtf( "%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1] )+"\r\n"; + message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port=" + +stringtf("%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1])+"\r\n"; break; - } case RTP_MULTICAST : - { message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n"; break; - } case RTP_RTSP : case RTP_RTSP_HTTP : - { message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n"; break; - } default: - { - Panic( "Got unexpected method %d", mMethod ); + Panic("Got unexpected method %d", mMethod); break; - } } - if ( !sendCommand( message ) ) - return( -1 ); - if ( !recvResponse( response ) ) - return( -1 ); + if ( !sendCommand(message) ) + return; + if ( !recvResponse(response) ) + return; - lines = split( response, "\r\n" ); + lines = Split(response, "\r\n"); std::string session; int timeout = 0; char transport[256] = ""; for ( size_t i = 0; i < lines.size(); i++ ) { - if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) ) { - StringVector sessionLine = split( lines[i].substr(9), ";" ); - session = trimSpaces( sessionLine[0] ); + if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) { + StringVector sessionLine = Split(lines[i].substr(9), ";"); + session = TrimSpaces(sessionLine[0]); if ( sessionLine.size() == 2 ) - sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout ); + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); } - sscanf( lines[i].c_str(), "Transport: %s", transport ); + sscanf(lines[i].c_str(), "Transport: %s", transport); } if ( session.empty() ) - Fatal( "Unable to get session identifier from response '%s'", response.c_str() ); + Fatal("Unable to get session identifier from response '%s'", response.c_str()); - Debug( 2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout ); + Debug(2, "Got RTSP session %s, timeout %d secs", session.c_str(), timeout); if ( !transport[0] ) - Fatal( "Unable to get transport details from response '%s'", response.c_str() ); + Fatal("Unable to get transport details from response '%s'", response.c_str()); - Debug( 2, "Got RTSP transport %s", transport ); + Debug(2, "Got RTSP transport %s", transport); std::string method = ""; int remotePorts[2] = { 0, 0 }; int remoteChannels[2] = { 0, 0 }; std::string distribution = ""; unsigned long ssrc = 0; - StringVector parts = split( transport, ";" ); + StringVector parts = Split(transport, ";"); for ( size_t i = 0; i < parts.size(); i++ ) { if ( parts[i] == "unicast" || parts[i] == "multicast" ) distribution = parts[i]; - else if ( startsWith( parts[i], "server_port=" ) ) { + else if (StartsWith(parts[i], "server_port=") ) { method = "RTP/UNICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector ports = Split(subparts[1], "-"); remotePorts[0] = strtol( ports[0].c_str(), nullptr, 10 ); remotePorts[1] = strtol( ports[1].c_str(), nullptr, 10 ); - } else if ( startsWith( parts[i], "interleaved=" ) ) { + } else if (StartsWith(parts[i], "interleaved=") ) { method = "RTP/RTSP"; - StringVector subparts = split( parts[i], "=" ); - StringVector channels = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector channels = Split(subparts[1], "-"); remoteChannels[0] = strtol( channels[0].c_str(), nullptr, 10 ); remoteChannels[1] = strtol( channels[1].c_str(), nullptr, 10 ); - } else if ( startsWith( parts[i], "port=" ) ) { + } else if (StartsWith(parts[i], "port=") ) { method = "RTP/MULTICAST"; - StringVector subparts = split( parts[i], "=" ); - StringVector ports = split( subparts[1], "-" ); + StringVector subparts = Split(parts[i], "="); + StringVector ports = Split(subparts[1], "-"); localPorts[0] = strtol( ports[0].c_str(), nullptr, 10 ); localPorts[1] = strtol( ports[1].c_str(), nullptr, 10 ); - } else if ( startsWith( parts[i], "destination=" ) ) { - StringVector subparts = split( parts[i], "=" ); + } else if (StartsWith(parts[i], "destination=") ) { + StringVector subparts = Split(parts[i], "="); localHost = subparts[1]; - } else if ( startsWith( parts[i], "ssrc=" ) ) { - StringVector subparts = split( parts[i], "=" ); + } else if (StartsWith(parts[i], "ssrc=") ) { + StringVector subparts = Split(parts[i], "="); ssrc = strtoll( subparts[1].c_str(), nullptr, 16 ); } } @@ -531,23 +523,23 @@ int RtspThread::run() { Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] ); message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n"; - if ( !sendCommand( message ) ) - return( -1 ); - if ( !recvResponse( response ) ) - return( -1 ); + if ( !sendCommand(message) ) + return; + if ( !recvResponse(response) ) + return; - lines = split( response, "\r\n" ); + lines = Split(response, "\r\n"); std::string rtpInfo; for ( size_t i = 0; i < lines.size(); i++ ) { - if ( ( lines[i].size() > 9 ) && ( lines[i].substr( 0, 9 ) == "RTP-Info:" ) ) - rtpInfo = trimSpaces( lines[i].substr( 9 ) ); - // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent - if ( ( lines[i].size() > 8 ) && ( lines[i].substr( 0, 8 ) == "Session:" ) && ( timeout == 0 ) ) { - StringVector sessionLine = split( lines[i].substr(9), ";" ); + if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) ) + rtpInfo = TrimSpaces(lines[i].substr(9)); + // Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent + if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) && ( timeout == 0 ) ) { + StringVector sessionLine = Split(lines[i].substr(9), ";"); if ( sessionLine.size() == 2 ) - sscanf( trimSpaces( sessionLine[1] ).c_str(), "timeout=%d", &timeout ); + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); if ( timeout > 0 ) - Debug( 2, "Got timeout %d secs from PLAY command response", timeout ); + Debug(2, "Got timeout %d secs from PLAY command response", timeout); } } @@ -559,18 +551,18 @@ int RtspThread::run() { } else { Debug( 2, "Got RTP Info %s", rtpInfo.c_str() ); // More than one stream can be included in the RTP Info - streams = split( rtpInfo.c_str(), "," ); + streams = Split(rtpInfo.c_str(), ","); for ( size_t i = 0; i < streams.size(); i++ ) { // We want the stream that matches the trackUrl we are using if ( streams[i].find(controlUrl.c_str()) != std::string::npos ) { // Parse the sequence and rtptime values - parts = split( streams[i].c_str(), ";" ); + parts = Split(streams[i].c_str(), ";"); for ( size_t j = 0; j < parts.size(); j++ ) { - if ( startsWith( parts[j], "seq=" ) ) { - StringVector subparts = split( parts[j], "=" ); + if (StartsWith(parts[j], "seq=") ) { + StringVector subparts = Split(parts[j], "="); seq = strtol( subparts[1].c_str(), nullptr, 10 ); - } else if ( startsWith( parts[j], "rtptime=" ) ) { - StringVector subparts = split( parts[j], "=" ); + } else if (StartsWith(parts[j], "rtptime=") ) { + StringVector subparts = Split(parts[j], "="); rtpTime = strtol( subparts[1].c_str(), nullptr, 10 ); } } @@ -594,17 +586,18 @@ int RtspThread::run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - rtpDataThread.start(); - rtpCtrlThread.start(); - - while( !mStop ) { + while (!mTerminate) { now = time(nullptr); // Send a keepalive message if the server supports this feature and we are close to the timeout expiration - Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", - sendKeepalive, timeout, now, lastKeepalive, (now-lastKeepalive) ); + Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, + sendKeepalive, + timeout, + static_cast(now), + static_cast(lastKeepalive), + static_cast(now - lastKeepalive)); if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) { if ( !sendCommand( message ) ) - return( -1 ); + return; lastKeepalive = now; } usleep( 100000 ); @@ -619,19 +612,16 @@ int RtspThread::run() { message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; if ( !sendCommand( message ) ) - return( -1 ); + return; if ( !recvResponse( response ) ) - return( -1 ); + return; - rtpDataThread.stop(); - rtpCtrlThread.stop(); + rtpDataThread.Stop(); + rtpCtrlThread.Stop(); //rtpDataThread.kill( SIGTERM ); //rtpCtrlThread.kill( SIGTERM ); - rtpDataThread.join(); - rtpCtrlThread.join(); - delete mSources[ssrc]; mSources.clear(); @@ -648,14 +638,14 @@ int RtspThread::run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - Select select( double(config.http_timeout)/1000.0 ); + ZM::Select select( double(config.http_timeout)/1000.0 ); select.addReader( &mRtspSocket ); Buffer buffer( ZM_NETWORK_BUFSIZ ); std::string keepaliveMessage = "OPTIONS "+mUrl+" RTSP/1.0\r\n"; std::string keepaliveResponse = "RTSP/1.0 200 OK\r\n"; - while ( !mStop && select.wait() >= 0 ) { - Select::CommsList readable = select.getReadable(); + while (!mTerminate && select.wait() >= 0) { + ZM::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { Error( "RTSP timed out" ); break; @@ -723,11 +713,16 @@ int RtspThread::run() { // FIXME: Is this really necessary when using tcp ? now = time(nullptr); // Send a keepalive message if the server supports this feature and we are close to the timeout expiration -Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepalive, timeout, now, lastKeepalive, (now-lastKeepalive) ); + Debug(5, "sendkeepalive %d, timeout %d, now: %" PRIi64 " last: %" PRIi64 " since: %" PRIi64, + sendKeepalive, + timeout, + static_cast(now), + static_cast(lastKeepalive), + static_cast(now - lastKeepalive)); if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) { if ( !sendCommand( message ) ) - return( -1 ); + return; lastKeepalive = now; } buffer.tidy( 1 ); @@ -742,7 +737,7 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali // Send a teardown message but don't expect a response as this may not be implemented on the server when using TCP message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; if ( !sendCommand( message ) ) - return( -1 ); + return; delete mSources[ssrc]; mSources.clear(); @@ -756,14 +751,12 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - rtpDataThread.start(); - rtpCtrlThread.start(); - while ( !mStop ) { + while (!mTerminate) { // Send a keepalive message if the server supports this feature and we are close to the timeout expiration if ( sendKeepalive && (timeout > 0) && ((time(nullptr)-lastKeepalive) > (timeout-5)) ) { if ( !sendCommand( message ) ) - return -1; + return; lastKeepalive = time(nullptr); } usleep(100000); @@ -777,15 +770,12 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali #endif message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; if ( !sendCommand(message) ) - return -1; + return; if ( !recvResponse(response) ) - return -1; + return; - rtpDataThread.stop(); - rtpCtrlThread.stop(); - - rtpDataThread.join(); - rtpCtrlThread.join(); + rtpDataThread.Stop(); + rtpCtrlThread.Stop(); delete mSources[ssrc]; mSources.clear(); @@ -798,7 +788,7 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali break; } - return 0; + return; } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_rtsp.h b/src/zm_rtsp.h index 14ef02010..6cb88d091 100644 --- a/src/zm_rtsp.h +++ b/src/zm_rtsp.h @@ -20,18 +20,16 @@ #ifndef ZM_RTSP_H #define ZM_RTSP_H -#include "zm.h" -#include "zm_ffmpeg.h" #include "zm_comms.h" -#include "zm_thread.h" #include "zm_rtp_source.h" #include "zm_rtsp_auth.h" #include "zm_sdp.h" - -#include +#include #include +#include +#include -class RtspThread : public Thread { +class RtspThread { public: typedef enum { RTP_UNICAST, RTP_MULTICAST, RTP_RTSP, RTP_RTSP_HTTP } RtspMethod; typedef enum { UNDEFINED, UNICAST, MULTICAST } RtspDist; @@ -70,8 +68,8 @@ private: std::string mHttpSession; ///< Only for RTSP over HTTP sessions - TcpInetClient mRtspSocket; - TcpInetClient mRtspSocket2; + ZM::TcpInetClient mRtspSocket; + ZM::TcpInetClient mRtspSocket2; SourceMap mSources; @@ -88,12 +86,14 @@ private: unsigned long mRtpTime; - bool mStop; + std::thread mThread; + std::atomic mTerminate; private: bool sendCommand( std::string message ); bool recvResponse( std::string &response ); - void checkAuthResponse(std::string &response); + void checkAuthResponse(std::string &response); + void Run(); public: RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host, const std::string &port, const std::string &path, const std::string &auth, bool rtsp_describe ); @@ -128,15 +128,10 @@ public: return( false ); return( iter->second->getFrame( frame ) ); } - int run(); - void stop() - { - mStop = true; - } - bool stopped() const - { - return( mStop ); - } + + void Stop() { mTerminate = true; } + bool IsStopped() const { return mTerminate; } + int getAddressFamily () { return mRtspSocket.getDomain(); diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 5cd05063c..4a1c6b2e2 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -14,15 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// +// -#include "zm.h" -#include "zm_utils.h" #include "zm_rtsp_auth.h" -#include -#include -#include +#include "zm_logger.h" +#include "zm_utils.h" +#include namespace zm { @@ -70,21 +68,21 @@ void Authenticator::authHandleHeader(std::string headerData) { else if ( strncasecmp(headerData.c_str(), digest_match, digest_match_len) == 0) { fAuthMethod = AUTH_DIGEST; Debug(2, "Set authMethod to Digest"); - StringVector subparts = split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ","); + StringVector subparts = Split(headerData.substr(digest_match_len, headerData.length() - digest_match_len), ","); // subparts are key="value" for ( size_t i = 0; i < subparts.size(); i++ ) { - StringVector kvPair = split(trimSpaces(subparts[i]), "="); - std::string key = trimSpaces(kvPair[0]); + StringVector kvPair = Split(TrimSpaces(subparts[i]), "="); + std::string key = TrimSpaces(kvPair[0]); if ( key == "realm" ) { - fRealm = trimSet(kvPair[1], "\""); + fRealm = Trim(kvPair[1], "\""); continue; } if ( key == "nonce" ) { - fNonce = trimSet(kvPair[1], "\""); + fNonce = Trim(kvPair[1], "\""); continue; } if ( key == "qop" ) { - fQop = trimSet(kvPair[1], "\""); + fQop = Trim(kvPair[1], "\""); continue; } } @@ -94,13 +92,13 @@ void Authenticator::authHandleHeader(std::string headerData) { } // end void Authenticator::authHandleHeader(std::string headerData) std::string Authenticator::quote( const std::string &src ) { - return replaceAll(replaceAll(src, "\\", "\\\\"), "\"", "\\\""); + return ReplaceAll(ReplaceAll(src, "\\", "\\\\"), "\"", "\\\""); } std::string Authenticator::getAuthHeader(std::string method, std::string uri) { std::string result = "Authorization: "; if ( fAuthMethod == AUTH_BASIC ) { - result += "Basic " + base64Encode(username() + ":" + password()); + result += "Basic " + Base64Encode(username() + ":" + password()); } else if ( fAuthMethod == AUTH_DIGEST ) { result += std::string("Digest ") + "username=\"" + quote(username()) + "\", realm=\"" + quote(realm()) + "\", " + @@ -129,7 +127,7 @@ std::string Authenticator::getAuthHeader(std::string method, std::string uri) { return result; } -std::string Authenticator::computeDigestResponse(std::string &method, std::string &uri) { +std::string Authenticator::computeDigestResponse(const std::string &method, const std::string &uri) { #if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT // The "response" field is computed as: // md5(md5(::)::md5(:)) @@ -194,9 +192,9 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin #endif // HAVE_DECL_MD5 } -void Authenticator::checkAuthResponse(std::string &response) { +void Authenticator::checkAuthResponse(const std::string &response) { std::string authLine; - StringVector lines = split(response, "\r\n"); + StringVector lines = Split(response, "\r\n"); const char* authenticate_match = "WWW-Authenticate:"; size_t authenticate_match_len = strlen(authenticate_match); @@ -207,13 +205,13 @@ void Authenticator::checkAuthResponse(std::string &response) { if ( strncasecmp(lines[i].c_str(), authenticate_match, authenticate_match_len) == 0 ) { authLine = lines[i]; - Debug(2, "Found auth line at %d:", i); + Debug(2, "Found auth line at %zu:", i); //break; } } if ( !authLine.empty() ) { Debug(2, "Analyze auth line %s", authLine.c_str()); - authHandleHeader(trimSpaces(authLine.substr(authenticate_match_len, authLine.length()-authenticate_match_len))); + authHandleHeader(TrimSpaces(authLine.substr(authenticate_match_len, authLine.length() - authenticate_match_len))); } else { Debug(2, "Didn't find auth line in %s", authLine.c_str()); } diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index 8e65746de..701b4b128 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -19,6 +19,9 @@ #ifndef ZM_RTSP_AUTH_H #define ZM_RTSP_AUTH_H +#include "zm_config.h" +#include + #if HAVE_GNUTLS_GNUTLS_H #include #endif @@ -43,10 +46,10 @@ public: std::string username() { return fUsername; } AuthMethod auth_method() const { return fAuthMethod; } - std::string computeDigestResponse( std::string &cmd, std::string &url ); + std::string computeDigestResponse(const std::string &cmd, const std::string &url); void authHandleHeader( std::string headerData ); std::string getAuthHeader( std::string method, std::string path ); - void checkAuthResponse(std::string &response); + void checkAuthResponse(const std::string &response); private: std::string password() { return fPassword; } diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp new file mode 100644 index 000000000..194cd57f2 --- /dev/null +++ b/src/zm_rtsp_server.cpp @@ -0,0 +1,357 @@ +// +// ZoneMinder RTSP Daemon +// Copyright (C) 2021 Isaac Connor +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +/* + +=head1 NAME + +zm_rtsp_server - The ZoneMinder Server + +=head1 SYNOPSIS + + zmc -m + zmc --monitor + zmc -h + zmc --help + zmc -v + zmc --version + +=head1 DESCRIPTION + +This binary's job is to connect to fifo's provided by local zmc processes +and provide that stream over rtsp + +=head1 OPTIONS + + -m, --monitor_id - ID of a monitor to stream + -h, --help - Display usage information + -v, --version - Print the installed version of ZoneMinder + +=cut + +*/ + +#include "zm.h" +#include "zm_db.h" +#include "zm_define.h" +#include "zm_monitor.h" +#include "zm_rtsp_server_authenticator.h" +#include "zm_rtsp_server_fifo_h264_source.h" +#include "zm_rtsp_server_fifo_adts_source.h" +#include "zm_signal.h" +#include "zm_time.h" +#include "zm_utils.h" + +#include +#include +#include + + +#include "xop/RtspServer.h" + +void Usage() { + fprintf(stderr, "zm_rtsp_server -m \n"); + + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -m, --monitor : We default to all monitors use this to specify just one\n"); + fprintf(stderr, " -h, --help : This screen\n"); + fprintf(stderr, " -v, --version : Report the installed version of ZoneMinder\n"); + exit(0); +} + +int main(int argc, char *argv[]) { + self = argv[0]; + + srand(getpid() * time(nullptr)); + + int monitor_id = -1; + + static struct option long_options[] = { + {"monitor", 1, nullptr, 'm'}, + {"help", 0, nullptr, 'h'}, + {"version", 0, nullptr, 'v'}, + {nullptr, 0, nullptr, 0} + }; + + while (1) { + int option_index = 0; + + int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index); + if ( c == -1 ) { + break; + } + + switch (c) { + case 'm': + monitor_id = atoi(optarg); + break; + case 'h': + case '?': + Usage(); + break; + case 'v': + std::cout << ZM_VERSION << "\n"; + exit(0); + default: + // fprintf(stderr, "?? getopt returned character code 0%o ??\n", c); + break; + } + } + + if (optind < argc) { + fprintf(stderr, "Extraneous options, "); + while (optind < argc) + printf("%s ", argv[optind++]); + printf("\n"); + Usage(); + } + + const char *log_id_string = "zm_rtsp_server"; + ///std::string log_id_string = std::string("zm_rtsp_server"); + ///if ( monitor_id > 0 ) log_id_string += stringtf("_m%d", monitor_id); + + logInit(log_id_string); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); + logInit(log_id_string); + if (!config.min_rtsp_port) { + Debug(1, "Not starting RTSP server because min_rtsp_port not set"); + exit(-1); + } + + HwCapsDetect(); + + std::string where = "`Function` != 'None' AND `RTSPServer` != false"; + if (staticConfig.SERVER_ID) + where += stringtf(" AND `ServerId`=%d", staticConfig.SERVER_ID); + if (monitor_id > 0) + where += stringtf(" AND `Id`=%d", monitor_id); + + Info("Starting RTSP Server version %s", ZM_VERSION); + zmSetDefaultHupHandler(); + zmSetDefaultTermHandler(); + zmSetDefaultDieHandler(); + + std::shared_ptr eventLoop(new xop::EventLoop()); + std::shared_ptr rtspServer = xop::RtspServer::Create(eventLoop.get()); + + if (config.opt_use_auth) { + std::shared_ptr authenticator(new ZM_RtspServer_Authenticator()); + rtspServer->SetAuthenticator(authenticator); + } + + if (!rtspServer->Start("0.0.0.0", config.min_rtsp_port)) { + Debug(1, "Failed starting RTSP server on port %d", config.min_rtsp_port); + exit(-1); + } + + //xop::MediaSession **sessions = new xop::MediaSession *[monitors.size()]; + + std::map sessions; + std::map video_sources; + std::map audio_sources; + std::map> monitors; + //std::vector> monitors; + //= Monitor::LoadMonitors(where, Monitor::QUERY); + + while (!zm_terminate) { + std::map> old_monitors = monitors; + + std::vector> new_monitors = Monitor::LoadMonitors(where, Monitor::QUERY); + for (const auto &monitor : new_monitors) { + if ( + (old_monitors.find(monitor->Id()) != old_monitors.end()) + and + (old_monitors[monitor->Id()]->GetRTSPStreamName() == monitor->GetRTSPStreamName()) + ) { + Debug(1, "Found monitor in oldmonitors, clearing it"); + old_monitors.erase(monitor->Id()); + } else { + Debug(1, "Adding monitor %d to monitors", monitor->Id()); + monitors[monitor->Id()] = monitor; + } + } + // Remove monitors that are no longer doing rtsp + for (auto it = old_monitors.begin(); it != old_monitors.end(); ++it) { + auto mid = it->first; + auto &monitor = it->second; + Debug(1, "Removing %d %s from monitors", monitor->Id(), monitor->Name()); + monitors.erase(mid); + if (sessions.find(mid) != sessions.end()) { + if (video_sources.find(monitor->Id()) != video_sources.end()) { + delete video_sources[monitor->Id()]; + video_sources.erase(monitor->Id()); + } + if (audio_sources.find(monitor->Id()) != audio_sources.end()) { + delete audio_sources[monitor->Id()]; + audio_sources.erase(monitor->Id()); + } + rtspServer->RemoveSession(sessions[mid]->GetMediaSessionId()); + //Debug(1, "Deleting session"); + //delete sessions[mid]; + sessions.erase(mid); + } + } + + for (auto it = monitors.begin(); it != monitors.end(); ++it) { + auto &monitor = it->second; + + if (!monitor->ShmValid()) { + Debug(1, "monitor %d !shmvalid", monitor->Id()); + monitor->disconnect(); + if (!monitor->connect()) { + Warning("Couldn't connect to monitor %d", monitor->Id()); + if (sessions.find(monitor->Id()) != sessions.end()) { + if (video_sources.find(monitor->Id()) != video_sources.end()) { + video_sources.erase(monitor->Id()); + } + if (audio_sources.find(monitor->Id()) != audio_sources.end()) { + audio_sources.erase(monitor->Id()); + } + rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); + delete sessions[monitor->Id()]; + sessions.erase(monitor->Id()); + } + + continue; + } + } + + + if (sessions.end() == sessions.find(monitor->Id())) { + Debug(1, "Monitor not found in sessions, opening it"); + std::string videoFifoPath = monitor->GetVideoFifoPath(); + if (videoFifoPath.empty()) { + Debug(1, "video fifo is empty. Skipping."); + continue; + } + + std::string streamname = monitor->GetRTSPStreamName(); + xop::MediaSession *session = sessions[monitor->Id()] = xop::MediaSession::CreateNew(streamname); + if (!session) { + Error("Unable to create session for %s", streamname.c_str()); + continue; + } + session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, const std::string &peer_ip, uint16_t peer_port){ + Debug(1, "RTSP client connect, ip=%s, port=%hu", peer_ip.c_str(), peer_port); + }); + + session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, const std::string &peer_ip, uint16_t peer_port) { + Debug(1, "RTSP client disconnect, ip=%s, port=%hu", peer_ip.c_str(), peer_port); + }); + + rtspServer->AddSession(session); + //char *url = rtspServer->rtspURL(session); + //Debug(1, "url is %s for stream %s", url, streamname.c_str()); + //delete[] url; + monitors[monitor->Id()] = monitor; + + Debug(1, "Adding video fifo %s", videoFifoPath.c_str()); + ZoneMinderFifoVideoSource *videoSource = nullptr; + + if (std::string::npos != videoFifoPath.find("h264")) { + session->AddSource(xop::channel_0, xop::H264Source::CreateNew()); + videoSource = new H264_ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, videoFifoPath); + } else if ( + std::string::npos != videoFifoPath.find("hevc") + or + std::string::npos != videoFifoPath.find("h265")) { + session->AddSource(xop::channel_0, xop::H265Source::CreateNew()); + videoSource = new H265_ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, videoFifoPath); + } else { + Warning("Unknown format in %s", videoFifoPath.c_str()); + } + if (videoSource == nullptr) { + Error("Unable to create source"); + rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); + delete sessions[monitor->Id()]; + sessions.erase(monitor->Id()); + continue; + } + video_sources[monitor->Id()] = videoSource; + videoSource->setWidth(monitor->Width()); + videoSource->setHeight(monitor->Height()); + + std::string audioFifoPath = monitor->GetAudioFifoPath(); + if (audioFifoPath.empty()) { + Debug(1, "audio fifo is empty. Skipping."); + continue; + } + Debug(1, "Adding audio fifo %s", audioFifoPath.c_str()); + + ZoneMinderFifoAudioSource *audioSource = nullptr; + + if (std::string::npos != audioFifoPath.find("aac")) { + Debug(1, "Adding aac source at %dHz %d channels", + monitor->GetAudioFrequency(), monitor->GetAudioChannels()); + session->AddSource(xop::channel_1, xop::AACSource::CreateNew( + monitor->GetAudioFrequency(), + monitor->GetAudioChannels(), + false /* has_adts */)); + audioSource = new ADTS_ZoneMinderFifoSource(rtspServer, + session->GetMediaSessionId(), xop::channel_1, audioFifoPath); + audioSource->setFrequency(monitor->GetAudioFrequency()); + audioSource->setChannels(monitor->GetAudioChannels()); + } else { + Warning("Unknown format in %s", audioFifoPath.c_str()); + } + if (audioSource == nullptr) { + Error("Unable to create source"); + } + audio_sources[monitor->Id()] = audioSource; + } // end if ! sessions[monitor->Id()] + } // end foreach monitor + + + sleep(5); + + if (zm_reload) { + logTerm(); + logInit(log_id_string); + zm_reload = false; + } // end if zm_reload + } // end while !zm_terminate + Info("RTSP Server shutting down"); + + for (const std::pair> &mon_pair : monitors) { + unsigned int i = mon_pair.first; + if (video_sources.find(i) != video_sources.end()) { + delete video_sources[i]; + } + if (audio_sources.find(i) != audio_sources.end()) { + delete audio_sources[i]; + } + if (sessions.find(i) != sessions.end()) { + Debug(1, "Removing session for %s", mon_pair.second->Name()); + rtspServer->RemoveSession(sessions[i]->GetMediaSessionId()); + sessions.erase(i); + } + } // end foreach monitor + + rtspServer->Stop(); + + sessions.clear(); + + Image::Deinitialise(); + logTerm(); + zmDbClose(); + + return 0; +} diff --git a/src/zm_rtsp_server_adts_source.cpp b/src/zm_rtsp_server_adts_source.cpp new file mode 100644 index 000000000..f6d1d81c0 --- /dev/null +++ b/src/zm_rtsp_server_adts_source.cpp @@ -0,0 +1,54 @@ +/* --------------------------------------------------------------------------- +** +** ADTS_DeviceSource.cpp +** +** ADTS Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_adts_source.h" + +#include "zm_config.h" +#include + +#if HAVE_RTSP_SERVER +// live555 +#include + +static unsigned const samplingFrequencyTable[16] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 +}; +// --------------------------------- +// ADTS ZoneMinder FramedSource +// --------------------------------- +// +ADTS_ZoneMinderDeviceSource::ADTS_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ) + : + ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize), + samplingFrequencyIndex(0), + channels( +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + stream->codecpar->channels +#else + stream->codec->channels +#endif + ) +{ + std::ostringstream os; + os << + "profile-level-id=1;" + "mode=AAC-hbr;sizelength=13;indexlength=3;" + "indexdeltalength=3" + //<< extradata2psets(nullptr, m_stream) + << "\r\n"; + m_auxLine.assign(os.str()); +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_adts_source.h b/src/zm_rtsp_server_adts_source.h new file mode 100644 index 000000000..b6f906a12 --- /dev/null +++ b/src/zm_rtsp_server_adts_source.h @@ -0,0 +1,67 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ADTS_ZoneMinderDeviceSource.h +** +** ADTS ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_ADTS_SOURCE_H +#define ZM_RTSP_SERVER_ADTS_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_device_source.h" + +#if HAVE_RTSP_SERVER +// --------------------------------- +// ADTS(AAC) ZoneMinder FramedSource +// --------------------------------- + +class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { + public: + static ADTS_ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream * stream, + unsigned int queueSize + ) { + return new ADTS_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize); + }; + protected: + ADTS_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ); + + virtual ~ADTS_ZoneMinderDeviceSource() {} + + /* + virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize); + virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); + */ + public: + int samplingFrequency() { return +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + m_stream->codecpar->sample_rate; +#else + m_stream->codec->sample_rate; +#endif + }; + const char *configStr() { return config.c_str(); }; + int numChannels() { + return channels; + } + + protected: + std::string config; + int samplingFrequencyIndex; + int channels; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_ADTS_SOURCE_H diff --git a/src/zm_rtsp_server_authenticator.h b/src/zm_rtsp_server_authenticator.h new file mode 100644 index 000000000..5793feb32 --- /dev/null +++ b/src/zm_rtsp_server_authenticator.h @@ -0,0 +1,95 @@ +#ifndef ZM_RTSP_SERVER_AUTHENTICATOR_H +#define ZM_RTSP_SERVER_AUTHENTICATOR_H + +#include "zm_config.h" +#include "zm_user.h" + +#include +#include + +#include "xop/Authenticator.h" +#include "xop/RtspMessage.h" + +#if HAVE_RTSP_SERVER + +class ZM_RtspServer_Authenticator : public xop::Authenticator { + public: + ZM_RtspServer_Authenticator() {}; + ~ZM_RtspServer_Authenticator() {}; + + bool Authenticate(std::shared_ptr request, std::string &nonce) { + + if (!config.opt_use_auth) { + Debug(1, "Not doing auth"); + return true; + } + std::string url = request->GetRtspUrl(); + Debug(1, "Doing auth %s", url.c_str()); + + User *user = nullptr; + + size_t found = url.find("?"); + if (found == std::string::npos) return false; + + std::string queryString = url.substr(found+1, std::string::npos); + +#if 0 + found = suffix_string.find("/"); + if ( found != std::string::npos ) + suffix_string = suffix_string.substr(0, found); +#endif + + Debug(1, "suffix %s", queryString.c_str()); + std::istringstream requestStream(queryString); + QueryString query(requestStream); + + if (query.has("jwt_token")) { + const QueryParameter *jwt_token = query.get("jwt_token"); + user = zmLoadTokenUser(jwt_token->firstValue(), false); + } else if (query.has("token")) { + const QueryParameter *jwt_token = query.get("token"); + user = zmLoadTokenUser(jwt_token->firstValue(), false); + } else if (strcmp(config.auth_relay, "none") == 0) { + if (query.has("username")) { + std::string username = query.get("username")->firstValue(); + if (checkUser(username.c_str())) { + user = zmLoadUser(username.c_str()); + } else { + Debug(1, "Bad username %s", username.c_str()); + } + } + } else { + if (query.has("auth")) { + std::string auth_hash = query.get("auth")->firstValue(); + if ( !auth_hash.empty() ) + user = zmLoadAuthUser(auth_hash.c_str(), config.auth_hash_ips); + } + Debug(1, "Query has username ? %d", query.has("username")); + if ((!user) and query.has("username") and query.has("password")) { + std::string username = query.get("username")->firstValue(); + std::string password = query.get("password")->firstValue(); + Debug(1, "username %s password %s", username.c_str(), password.c_str()); + user = zmLoadUser(username.c_str(), password.c_str()); + } + } // end if query string + + if (user) { + Debug(1, "Authenticated"); + delete user; + return true; + } + return false; + } + + size_t GetFailedResponse( + std::shared_ptr request, + std::shared_ptr buf, + size_t size) { + return request->BuildUnauthorizedRes(buf.get(), size); + } + +}; // end class ZM_RtspServer_Authenticator + +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_AUTHENTICATOR_H diff --git a/src/zm_rtsp_server_device_source.cpp b/src/zm_rtsp_server_device_source.cpp new file mode 100644 index 000000000..abd1f8381 --- /dev/null +++ b/src/zm_rtsp_server_device_source.cpp @@ -0,0 +1,217 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** +** ZoneMinder Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_device_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include "zm_rtsp_server_frame.h" +#include "zm_signal.h" + +#if HAVE_RTSP_SERVER +ZoneMinderDeviceSource::ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize + ) : + FramedSource(env), + m_stream(stream), + m_monitor(std::move(monitor)), + m_packetqueue(nullptr), + m_packetqueue_it(nullptr), + m_queueSize(queueSize) +{ + m_eventTriggerId = envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub); + memset(&m_thid, 0, sizeof(m_thid)); + memset(&m_mutex, 0, sizeof(m_mutex)); + if ( m_monitor ) { + m_packetqueue = m_monitor->GetPacketQueue(); + if ( !m_packetqueue ) { + Fatal("No packetqueue"); + } + pthread_mutex_init(&m_mutex, nullptr); + pthread_create(&m_thid, nullptr, threadStub, this); + } else { + Error("No monitor in ZoneMinderDeviceSource"); + } +} + +ZoneMinderDeviceSource::~ZoneMinderDeviceSource() { + stop = 1; + envir().taskScheduler().deleteEventTrigger(m_eventTriggerId); + pthread_join(m_thid, nullptr); + while ( m_captureQueue.size() ) { + NAL_Frame * f = m_captureQueue.front(); + m_captureQueue.pop_front(); + delete f; + } + + pthread_mutex_destroy(&m_mutex); +} + +// thread mainloop +void* ZoneMinderDeviceSource::thread() { + stop = 0; + + while ( !stop ) { + getNextFrame(); + } + return nullptr; +} + +// getting FrameSource callback +void ZoneMinderDeviceSource::doGetNextFrame() { + deliverFrame(); +} + +// stopping FrameSource callback +void ZoneMinderDeviceSource::doStopGettingFrames() { + stop = 1; + Debug(1, "ZoneMinderDeviceSource::doStopGettingFrames"); + FramedSource::doStopGettingFrames(); +} + +// deliver frame to the sink +void ZoneMinderDeviceSource::deliverFrame() { + if ( !isCurrentlyAwaitingData() ) { + Debug(4, "not awaiting data"); + return; + } + + pthread_mutex_lock(&m_mutex); + if ( m_captureQueue.empty() ) { + Debug(4, "Queue is empty"); + pthread_mutex_unlock(&m_mutex); + return; + } + + NAL_Frame *frame = m_captureQueue.front(); + m_captureQueue.pop_front(); + pthread_mutex_unlock(&m_mutex); + + fDurationInMicroseconds = 0; + fFrameSize = 0; + + unsigned int nal_size = frame->size(); + + if ( nal_size > fMaxSize ) { + fFrameSize = fMaxSize; + fNumTruncatedBytes = nal_size - fMaxSize; + } else { + fFrameSize = nal_size; + } + Debug(2, "deliverFrame stream: %d timestamp: %ld.%06ld size: %d queuesize: %d", + m_stream->index, + frame->m_timestamp.tv_sec, frame->m_timestamp.tv_usec, + fFrameSize, + m_captureQueue.size() + ); + + fPresentationTime = frame->m_timestamp; + memcpy(fTo, frame->buffer(), fFrameSize); + + if ( fFrameSize > 0 ) { + // send Frame to the consumer + FramedSource::afterGetting(this); + } + delete frame; +} // end void ZoneMinderDeviceSource::deliverFrame() + +// FrameSource callback on read event +void ZoneMinderDeviceSource::incomingPacketHandler() { + if ( this->getNextFrame() <= 0 ) { + handleClosure(this); + } +} + +// read from monitor +int ZoneMinderDeviceSource::getNextFrame() { + if ( zm_terminate ) + return -1; + + if ( !m_packetqueue_it ) { + m_packetqueue_it = m_packetqueue->get_video_it(true); + } + ZMPacket *zm_packet = m_packetqueue->get_packet(m_packetqueue_it); + while ( zm_packet and (zm_packet->packet.stream_index != m_stream->index) ) { + zm_packet->unlock(); + // We want our stream to start at the same it as the video + // but if this is an audio stream we need to increment past that first packet + Debug(4, "Have audio packet, skipping"); + m_packetqueue->increment_it(m_packetqueue_it, m_stream->index); + zm_packet = m_packetqueue->get_packet(m_packetqueue_it); + } + if ( !zm_packet ) { + Debug(1, "null zm_packet %p", zm_packet); + return -1; + } + // packet is locked + AVPacket *pkt = &zm_packet->packet; + + // Convert pts to timeval + int64_t pts = av_rescale_q(pkt->dts, m_stream->time_base, AV_TIME_BASE_Q); + timeval tv = { pts/1000000, pts%1000000 }; + ZM_DUMP_STREAM_PACKET(m_stream, (*pkt), "rtspServer"); + Debug(2, "pts %" PRId64 " pkt.pts %" PRId64 " tv %d.%d", pts, pkt->pts, tv.tv_sec, tv.tv_usec); + + std::list< std::pair > framesList = this->splitFrames(pkt->data, pkt->size); + zm_packet->unlock(); + zm_packet = nullptr;// we no longer have the lock so shouldn't be accessing it + m_packetqueue->increment_it(m_packetqueue_it, m_stream->index); + + while ( framesList.size() ) { + std::pair nal = framesList.front(); + framesList.pop_front(); + + NAL_Frame *frame = new NAL_Frame(nal.first, nal.second, tv); + + pthread_mutex_lock(&m_mutex); + if ( m_captureQueue.size() ) { + NAL_Frame * f = m_captureQueue.front(); + while ( m_captureQueue.size() and ((f->m_timestamp.tv_sec - tv.tv_sec) > 10) ) { + m_captureQueue.pop_front(); + delete f; + f = m_captureQueue.front(); + } + } +#if 0 + while ( m_captureQueue.size() >= m_queueSize ) { + Debug(2, "Queue full dropping frame %d", m_captureQueue.size()); + NAL_Frame * f = m_captureQueue.front(); + m_captureQueue.pop_front(); + delete f; + } +#endif + m_captureQueue.push_back(frame); + pthread_mutex_unlock(&m_mutex); + + // post an event to ask to deliver the frame + envir().taskScheduler().triggerEvent(m_eventTriggerId, this); + } // end while we get frame from data + return 1; +} + +// split packet in frames +std::list< std::pair > ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { + std::list< std::pair > frameList; + if ( frame != nullptr ) { + frameList.push_back(std::pair(frame, frameSize)); + } + return frameList; +} + +// extract a frame +unsigned char* ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + outsize = size; + size = 0; + return frame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_device_source.h b/src/zm_rtsp_server_device_source.h new file mode 100644 index 000000000..b74a8a606 --- /dev/null +++ b/src/zm_rtsp_server_device_source.h @@ -0,0 +1,78 @@ +/* --------------------------------------------------------------------------- +** +** DeviceSource.h +** +** live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_DEVICE_SOURCE_H +#define ZM_RTSP_SERVER_DEVICE_SOURCE_H + +#include "zm_config.h" +#include "zm_define.h" +#include "zm_monitor.h" +#include +#include +#include + +#if HAVE_RTSP_SERVER +#include + +class NAL_Frame; + +class ZoneMinderDeviceSource: public FramedSource { + + public: + static ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream * stream, + unsigned int queueSize + ) { + return new ZoneMinderDeviceSource(env, monitor, stream, queueSize); + }; + std::string getAuxLine() { return m_auxLine; }; + int getWidth() { return m_monitor->Width(); }; + int getHeight() { return m_monitor->Height(); }; + + protected: + ZoneMinderDeviceSource(UsageEnvironment& env, std::shared_ptr monitor, AVStream * stream, unsigned int queueSize); + virtual ~ZoneMinderDeviceSource(); + + protected: + static void* threadStub(void* clientData) { return ((ZoneMinderDeviceSource*) clientData)->thread();}; + void* thread(); + static void deliverFrameStub(void* clientData) {((ZoneMinderDeviceSource*) clientData)->deliverFrame();}; + void deliverFrame(); + static void incomingPacketHandlerStub(void* clientData, int mask) { ((ZoneMinderDeviceSource*) clientData)->incomingPacketHandler(); }; + void incomingPacketHandler(); + int getNextFrame(); + void processFrame(char * frame, int frameSize, const timeval &ref); + void queueFrame(char * frame, int frameSize, const timeval &tv); + + // split packet in frames + virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); + + // overide FramedSource + virtual void doGetNextFrame(); + virtual void doStopGettingFrames(); + virtual unsigned char *extractFrame(unsigned char *data, size_t& size, size_t& outsize); + + protected: + std::list m_captureQueue; + EventTriggerId m_eventTriggerId; + AVStream *m_stream; + std::shared_ptr m_monitor; + PacketQueue *m_packetqueue; + std::list::iterator *m_packetqueue_it; + + unsigned int m_queueSize; + pthread_t m_thid; + pthread_mutex_t m_mutex; + std::string m_auxLine; + int stop; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_DEVICE_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_adts_source.cpp b/src/zm_rtsp_server_fifo_adts_source.cpp new file mode 100644 index 000000000..6c01371e5 --- /dev/null +++ b/src/zm_rtsp_server_fifo_adts_source.cpp @@ -0,0 +1,51 @@ +/* --------------------------------------------------------------------------- +** +** ADTS_FifoSource.cpp +** +** ADTS Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_logger.h" +#include "zm_rtsp_server_fifo_adts_source.h" + +#include +#include + +#if HAVE_RTSP_SERVER + +// --------------------------------- +// ADTS ZoneMinder FramedSource +// --------------------------------- +// +ADTS_ZoneMinderFifoSource::ADTS_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ) + : + ZoneMinderFifoAudioSource(rtspServer, sessionId, channelId, fifo) +{ +#if 0 + int profile = 0; + + unsigned char audioSpecificConfig[2]; + u_int8_t const audioObjectType = profile + 1; + audioSpecificConfig[0] = (audioObjectType<<3) | (samplingFrequencyIndex>>1); + audioSpecificConfig[1] = (samplingFrequencyIndex<<7) | (channels<<3); + + std::ostringstream os; + os << + "profile-level-id=1;" + "mode=AAC-hbr;sizelength=13;indexlength=3;" + "indexdeltalength=3;config=" << std::hex << std::setw(2) << std::setfill('0') << audioSpecificConfig[0] + << std::hex << std::setw(2) << std::setfill('0') << audioSpecificConfig[1] + << "\r\n"; + // Construct the 'AudioSpecificConfig', and from it, the corresponding ASCII string: + + m_auxLine.assign(os.str()); + Debug(1, "m_auxline is %s", m_auxLine.c_str()); +#endif +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_adts_source.h b/src/zm_rtsp_server_fifo_adts_source.h new file mode 100644 index 000000000..0bbe0890c --- /dev/null +++ b/src/zm_rtsp_server_fifo_adts_source.h @@ -0,0 +1,36 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ADTS_ZoneMinderFifoSource.h +** +** ADTS ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_ADTS_FIFO_SOURCE_H +#define ZM_RTSP_SERVER_ADTS_FIFO_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_audio_source.h" + +#if HAVE_RTSP_SERVER +// --------------------------------- +// ADTS(AAC) ZoneMinder FramedSource +// --------------------------------- + +class ADTS_ZoneMinderFifoSource : public ZoneMinderFifoAudioSource { + public: + ADTS_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ); + + virtual ~ADTS_ZoneMinderFifoSource() {} +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_ADTS_FIFO_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_audio_source.cpp b/src/zm_rtsp_server_fifo_audio_source.cpp new file mode 100644 index 000000000..96ccf2075 --- /dev/null +++ b/src/zm_rtsp_server_fifo_audio_source.cpp @@ -0,0 +1,48 @@ +/* --------------------------------------------------------------------------- +** +** ADTS_FifoSource.cpp +** +** ADTS Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_audio_source.h" + +#if HAVE_RTSP_SERVER + +static const int samplingFrequencyTable[16] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 +}; +// --------------------------------- +// ADTS ZoneMinder FramedSource +// --------------------------------- +// +ZoneMinderFifoAudioSource::ZoneMinderFifoAudioSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const std::string &fifo + ) + : + ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo), + samplingFrequencyIndex(-1), + frequency(-1), + channels(1) +{ +} +int ZoneMinderFifoAudioSource::getFrequencyIndex() { + for (int i=0; i<16; i++) + if (samplingFrequencyTable[i] == frequency) return i; + return -1; +} + +void ZoneMinderFifoAudioSource::PushFrame(const uint8_t *data, size_t size, int64_t pts) { + xop::AVFrame frame(data, size); + frame.type = xop::AUDIO_FRAME; + frame.timestamp = av_rescale_q(pts, AV_TIME_BASE_Q, m_timeBase); + m_rtspServer->PushFrame(m_sessionId, m_channelId, frame); +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_audio_source.h b/src/zm_rtsp_server_fifo_audio_source.h new file mode 100644 index 000000000..f5610052d --- /dev/null +++ b/src/zm_rtsp_server_fifo_audio_source.h @@ -0,0 +1,56 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ADTS_ZoneMinderFifoSource.h +** +** ADTS ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_FIFO_AUDIO_SOURCE_H +#define ZM_RTSP_SERVER_FIFO_AUDIO_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" + +#if HAVE_RTSP_SERVER +// --------------------------------- +// ZoneMinder AUDIO FramedSource +// --------------------------------- + +class ZoneMinderFifoAudioSource : public ZoneMinderFifoSource { + public: + ZoneMinderFifoAudioSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const std::string &fifo + ); + + virtual ~ZoneMinderFifoAudioSource() {} + + void setFrequency(int p_frequency) { + frequency = p_frequency; + samplingFrequencyIndex = getFrequencyIndex(); + m_timeBase = {1, frequency}; + }; + int getFrequency() { return frequency; }; + int getFrequencyIndex(); + const char *configStr() const { return config.c_str(); }; + void setChannels(int p_channels) { channels = p_channels; }; + int getChannels() const { return channels; }; + + protected: + void PushFrame(const uint8_t *data, size_t size, int64_t pts) override; + + protected: + std::string config; + int samplingFrequencyIndex; + int frequency; + int channels; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_FIFO_AUDIO_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_h264_source.cpp b/src/zm_rtsp_server_fifo_h264_source.cpp new file mode 100644 index 000000000..9963ea26c --- /dev/null +++ b/src/zm_rtsp_server_fifo_h264_source.cpp @@ -0,0 +1,239 @@ +/* --------------------------------------------------------------------------- +** +** H264_FifoSource.cpp +** +** H264 Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_h264_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include +#include + +#if HAVE_RTSP_SERVER + +// --------------------------------- +// H264 ZoneMinder FramedSource +// --------------------------------- +// +H264_ZoneMinderFifoSource::H264_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ) + : H26X_ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) +{ + // extradata appears to simply be the SPS and PPS NAL's + //this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size); + m_hType = 264; +} + +// split packet into frames +std::list< std::pair > H264_ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { +#if 0 + bool updateAux = false; + switch ( m_frameType & 0x1F ) { + case 7: + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer, size); + updateAux = true; + break; + case 8: + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer, size); + updateAux = true; + break; + case 5: + Debug(4, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + break; + } + + if ( updateAux and !m_sps.empty() and !m_pps.empty() ) { + u_int32_t profile_level_id = 0; + if ( m_sps.size() >= 4 ) profile_level_id = (m_sps[1]<<16)|(m_sps[2]<<8)|m_sps[3]; + + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "profile-level-id=" << std::hex << std::setw(6) << std::setfill('0') << profile_level_id; + os << ";sprop-parameter-sets=" << sps_base64 << "," << pps_base64 << "\r\n"; + if (!(m_width and m_height)) + os << "a=x-dimensions:" << m_width << "," << m_height << "\r\n"; + m_auxLine.assign(os.str()); + Debug(3, "auxLine: %s", m_auxLine.c_str()); + + delete [] sps_base64; + delete [] pps_base64; + } +#endif + frameList.push_back(std::pair(buffer, size)); + if (!bufSize) break; + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + frameSize = bufSize; + return frameList; +} + +H265_ZoneMinderFifoSource::H265_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ) + : H26X_ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) +{ + // extradata appears to simply be the SPS and PPS NAL's + // this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size); + m_hType = 265; +} + +// split packet in frames +std::list< std::pair > +H265_ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { +#if 0 + bool updateAux = false; + switch ((m_frameType&0x7E)>>1) { + case 32: + Debug(4, "VPS_Size: %d bufSize %d", size, bufSize); + m_vps.assign((char*)buffer,size); + updateAux = true; + break; + case 33: + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer,size); + updateAux = true; + break; + case 34: + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer,size); + updateAux = true; + break; + case 19: + case 20: + Debug(4, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_vps.c_str(), m_vps.size())); + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + //Debug(4, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); + break; + } + + if ( updateAux and !m_vps.empty() and !m_sps.empty() and !m_pps.empty() ) { + char* vps_base64 = base64Encode(m_vps.c_str(), m_vps.size()); + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "sprop-vps=" << vps_base64; + os << ";sprop-sps=" << sps_base64; + os << ";sprop-pps=" << pps_base64; + os << "\r\n" << "a=x-dimensions:" << m_width << "," << m_height << "\r\n"; + m_auxLine.assign(os.str()); + Debug(1, "Assigned h265 auxLine to %s", m_auxLine.c_str()); + + delete [] vps_base64; + delete [] sps_base64; + delete [] pps_base64; + } +#endif + frameList.push_back(std::pair(buffer, size)); + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + frameSize = bufSize; + return frameList; +} // end H265_ZoneMinderFifoSource::splitFrames(unsigned char* frame, unsigned frameSize) + +unsigned char * H26X_ZoneMinderFifoSource::findMarker( + unsigned char *frame, size_t size, size_t &length + ) { + //Debug(1, "findMarker %p %d", frame, size); + unsigned char *start = nullptr; + for ( size_t i = 0; i < size-2; i += 1 ) { + //Debug(1, "%d: %d %d %d", i, frame[i], frame[i+1], frame[i+2]); + if ( (frame[i] == 0) and (frame[i+1]) == 0 and (frame[i+2] == 1) ) { + if ( i and (frame[i-1] == 0) ) { + start = frame + i - 1; + length = sizeof(H264marker); + } else { + start = frame + i; + length = sizeof(H264shortmarker); + } + break; + } + } + return start; +} + +// extract a frame +unsigned char* H26X_ZoneMinderFifoSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + unsigned char *outFrame = nullptr; + Debug(4, "ExtractFrame: %p %zu", frame, size); + outsize = 0; + size_t markerLength = 0; + size_t endMarkerLength = 0; + m_frameType = 0; + unsigned char *startFrame = nullptr; + if ( size >= 3 ) + startFrame = this->findMarker(frame, size, markerLength); + if ( startFrame != nullptr ) { + Debug(4, "startFrame: %p marker Length %zu", startFrame, markerLength); + m_frameType = startFrame[markerLength]; + + int remainingSize = size-(startFrame-frame+markerLength); + unsigned char *endFrame = nullptr; + if ( remainingSize > 3 ) { + endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength); + } + Debug(4, "endFrame: %p marker Length %zu, remaining size %d", endFrame, endMarkerLength, remainingSize); + + if ( m_keepMarker ) { + size -= startFrame-frame; + outFrame = startFrame; + } else { + size -= startFrame-frame+markerLength; + outFrame = &startFrame[markerLength]; + } + + if ( endFrame != nullptr ) { + outsize = endFrame - outFrame; + } else { + outsize = size; + } + size -= outsize; + Debug(4, "Have frame type: %d size %zu, keepmarker %d", m_frameType, outsize, m_keepMarker); + } else if ( size >= sizeof(H264shortmarker) ) { + Info("No marker found size %zu", size); + } + + return outFrame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_h264_source.h b/src/zm_rtsp_server_fifo_h264_source.h new file mode 100644 index 000000000..d771c9ac7 --- /dev/null +++ b/src/zm_rtsp_server_fifo_h264_source.h @@ -0,0 +1,77 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** H264_ZoneMinderFifoSource.h +** +** H264 ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_H264_FIFO_SOURCE_H +#define ZM_RTSP_H264_FIFO_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_video_source.h" + +// --------------------------------- +// H264 ZoneMinder FramedSource +// --------------------------------- +#if HAVE_RTSP_SERVER +class H26X_ZoneMinderFifoSource : public ZoneMinderFifoVideoSource { + public: + H26X_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const std::string &fifo + ) + : + ZoneMinderFifoVideoSource(rtspServer, sessionId, channelId, fifo), + m_keepMarker(false), + m_frameType(0) { } + + virtual ~H26X_ZoneMinderFifoSource() {} + + virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize) override; + virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); + + protected: + std::string m_sps; + std::string m_pps; + bool m_keepMarker; + int m_frameType; +}; + +class H264_ZoneMinderFifoSource : public H26X_ZoneMinderFifoSource { + public: + H264_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ); + + // overide ZoneMinderFifoSource + virtual std::list< std::pair > splitFrames(unsigned char* frame, size_t &frameSize) override; +}; + +class H265_ZoneMinderFifoSource : public H26X_ZoneMinderFifoSource { + public: + H265_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ); + + // overide ZoneMinderFifoSource + virtual std::list< std::pair > splitFrames(unsigned char* frame, size_t &frameSize) override; + + protected: + std::string m_vps; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_H264_FIFO_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_source.cpp b/src/zm_rtsp_server_fifo_source.cpp new file mode 100644 index 000000000..6a6066bcf --- /dev/null +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -0,0 +1,284 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** +** ZoneMinder Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_source.h" +#include "zm_rtsp_server_frame.h" + +#include "zm_config.h" +#include "zm_ffmpeg.h" +#include "zm_logger.h" +#include "zm_signal.h" + +#include +#include + +#if HAVE_RTSP_SERVER +ZoneMinderFifoSource::ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ) : + stop_(false), + m_rtspServer(rtspServer), + m_sessionId(sessionId), + m_channelId(channelId), + m_fifo(fifo), + m_fd(-1), + m_hType(0) +{ + read_thread_ = std::thread(&ZoneMinderFifoSource::ReadRun, this); + write_thread_ = std::thread(&ZoneMinderFifoSource::WriteRun, this); +} + +ZoneMinderFifoSource::~ZoneMinderFifoSource() { + Debug(1, "Deleting Fifo Source"); + Stop(); + if (read_thread_.joinable()) + read_thread_.join(); + if (write_thread_.joinable()) + write_thread_.join(); + Debug(1, "Deleting Fifo Source done"); +} + +// thread mainloop +void ZoneMinderFifoSource::ReadRun() { + if (stop_) Warning("bad value for stop_ in ReadRun"); + while (!stop_) { + if (getNextFrame() < 0) { + Debug(1, "Sleeping"); + sleep(1); + } + } +} +void ZoneMinderFifoSource::WriteRun() { + size_t maxNalSize = 1400; + + if (stop_) Warning("bad value for stop_ in WriteRun"); + while (!stop_) { + NAL_Frame *nal = nullptr; + while (!stop_ and !nal) { + std::unique_lock lck(mutex_); + if (m_nalQueue.empty()) { + Debug(3, "waiting"); + condition_.wait(lck); + } + if (!m_nalQueue.empty()) { + nal = m_nalQueue.front(); + m_nalQueue.pop(); + } + } + + if (nal) { + if (1 and (nal->size() > maxNalSize)) { + Debug(1, "Splitting NAL %zu", nal->size()); + size_t nalRemaining = nal->size(); + u_int8_t *nalSrc = nal->buffer(); + + int fuNalSize = maxNalSize; + // ? nalRemaining : maxNalSize; + NAL_Frame fuNal(nullptr, fuNalSize, nal->pts()); + memcpy(fuNal.buffer()+1, nalSrc, fuNalSize-1); + + if (m_hType == 264) { + fuNal.buffer()[0] = (nalSrc[0] & 0xE0) | 28; // FU indicator + fuNal.buffer()[1] = 0x80 | (nalSrc[0] & 0x1F); // FU header (with S bit) + } else { // 265 + u_int8_t nalUnitType = (nalSrc[0]&0x7E)>>1; + fuNal.buffer()[0] = (nalSrc[0] & 0x81) | (49<<1); // Payload header (1st byte) + fuNal.buffer()[1] = nalSrc[1]; // Payload header (2nd byte) + fuNal.buffer()[2] = 0x80 | nalUnitType; // FU header (with S bit) + } + PushFrame(fuNal.buffer(), fuNal.size(), fuNal.pts()); + nalRemaining -= maxNalSize-1; + nalSrc += maxNalSize-1; + int nal_count = 0; + + int headerSize = 0; + if (m_hType == 264) { + fuNal.buffer()[1] = fuNal.buffer()[1]&~0x80; // FU header (no S bit) + headerSize = 2; + } else { // 265 + fuNal.buffer()[2] = fuNal.buffer()[2]&~0x80; // FU header (no S bit) + headerSize = 3; + } + while (nalRemaining) { + if ( nalRemaining < maxNalSize ) { + // This is the last fragment: + fuNal.buffer()[headerSize-1] |= 0x40; // set the E bit in the FU header + } + fuNalSize = (nalRemaining < maxNalSize-headerSize) ? nalRemaining : maxNalSize-headerSize; + fuNal.size(fuNalSize+headerSize); + memcpy(fuNal.buffer()+headerSize, nalSrc, fuNalSize); + + PushFrame(fuNal.buffer(), fuNal.size(), fuNal.pts()); + nalRemaining -= fuNalSize; + nalSrc += fuNalSize; + nal_count += 1; + } + Debug(1, "Sending %d NALs @ %zu and 1 @ %zu", nal_count, maxNalSize, fuNal.size()); + } else { + Debug(3, "Pushing nal of size %zu at %" PRId64, nal->size(), nal->pts()); + PushFrame(nal->buffer(), nal->size(), nal->pts()); + } + delete nal; + nal = nullptr; + Debug(3, "Done Pushing nal"); + } // end if nal + } // end while !_stop +} + +// read from monitor +int ZoneMinderFifoSource::getNextFrame() { + if (zm_terminate or stop_) { + Debug(1, "Terminating %d %d", zm_terminate, (stop_==true?1:0)); + return -1; + } + + if (m_fd == -1) { + Debug(1, "Opening fifo %s", m_fifo.c_str()); + m_fd = open(m_fifo.c_str(), O_RDONLY); + if (m_fd < 0) { + Error("Can't open %s: %s", m_fifo.c_str(), strerror(errno)); + return -1; + } + } + + int bytes_read = m_buffer.read_into(m_fd, 32); + //int bytes_read = m_buffer.read_into(m_fd, 4096, {1,0}); + if (bytes_read == 0) { + Debug(3, "No bytes read"); + sleep(1); + return -1; + } + if (bytes_read < 0) { + Error("Problem during reading: %s", strerror(errno)); + ::close(m_fd); + m_fd = -1; + return -1; + } + + Debug(3, "%s bytes read %d bytes, buffer size %u", m_fifo.c_str(), bytes_read, m_buffer.size()); + while (m_buffer.size()) { + unsigned int data_size = 0; + int64_t pts; + unsigned char *header_end = nullptr; + unsigned char *header_start = nullptr; + + if ((header_start = (unsigned char *)memmem(m_buffer.head(), m_buffer.size(), "ZM", 2))) { + // next step, look for \n + header_end = (unsigned char *)memchr(header_start, '\n', m_buffer.tail()-header_start); + if (!header_end) { + // Must not have enough data. So... keep all. + Debug(1, "Didn't find newline buffer size is %d", m_buffer.size()); + return 0; + } + + unsigned int header_size = header_end-header_start; + char *header = new char[header_size+1]; + strncpy(header, reinterpret_cast(header_start), header_size); + header[header_size] = '\0'; + + char *content_length_ptr = strchr(header, ' '); + if (!content_length_ptr) { + Debug(1, "Didn't find space delineating size in %s", header); + m_buffer.consume((header_start-m_buffer.head()) + 2); + delete[] header; + return 0; + } + *content_length_ptr = '\0'; + content_length_ptr ++; + char *pts_ptr = strchr(content_length_ptr, ' '); + if (!pts_ptr) { + m_buffer.consume((header_start-m_buffer.head()) + 2); + Debug(1, "Didn't find space delineating pts in %s", header); + delete[] header; + return 0; + } + *pts_ptr = '\0'; + pts_ptr ++; + data_size = atoi(content_length_ptr); + pts = strtoll(pts_ptr, nullptr, 10); + Debug(4, "ZM Packet %s header_size %d packet size %u pts %s %" PRId64, header, header_size, data_size, pts_ptr, pts); + delete[] header; + } else { + Debug(1, "ZM header not found in %d of buffer:%s.", m_buffer.size(), m_buffer.head()); + m_buffer.clear(); + return 0; + } + if (header_start != m_buffer) { + Debug(4, "ZM Packet didn't start at beginning of buffer %ld. %c%c", + header_start - m_buffer.head(), m_buffer[0], m_buffer[1]); + } + + // read_into may invalidate packet_start + unsigned int header_size = (header_end+1) /*packet_start*/ - m_buffer.head(); // includes any bytes before header + + int bytes_needed = data_size - (m_buffer.size() - header_size); + if (bytes_needed > 0) { + Debug(4, "Need another %d bytes. Trying to read them", bytes_needed); + while (bytes_needed) { + int bytes_read = m_buffer.read_into(m_fd, bytes_needed); + if (bytes_read <= 0) { + Debug(1, "Failed to read another %d bytes, got %d.", bytes_needed, bytes_read); + return -1; + } + + if (bytes_read != bytes_needed) { + Debug(4, "Failed to read another %d bytes, got %d.", bytes_needed, bytes_read); + } + bytes_needed -= bytes_read; + } // end while bytes_neeeded + } + //Debug(4, "Consuming %d", header_size); + //m_buffer.consume(header_size); + + unsigned char *packet_start = m_buffer.head() + header_size; + size_t bytes_remaining = data_size; + std::list< std::pair > framesList = this->splitFrames(packet_start, bytes_remaining); + m_buffer.consume(data_size+header_size); + Debug(3, "Got %zu frames, consuming %d bytes, remaining %zu", + framesList.size(), + data_size + header_size, + bytes_remaining); + + { + std::unique_lock lck(mutex_); + Debug(3, "have lock"); + while (framesList.size()) { + std::pair nal = framesList.front(); + framesList.pop_front(); + NAL_Frame *Nal = new NAL_Frame(nal.first, nal.second, pts); + m_nalQueue.push(Nal); + } + } + Debug(1, "notifying"); + condition_.notify_all(); + } // end while m_buffer.size() + return 1; +} + +// split packet in frames +std::list< std::pair > ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list< std::pair > frameList; + if ( frame != nullptr ) { + frameList.push_back(std::pair(frame, frameSize)); + } + frameSize = 0; + return frameList; +} + +// extract a frame +unsigned char* ZoneMinderFifoSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + outsize = size; + size = 0; + return frame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_source.h b/src/zm_rtsp_server_fifo_source.h new file mode 100644 index 000000000..eb9fd4fe9 --- /dev/null +++ b/src/zm_rtsp_server_fifo_source.h @@ -0,0 +1,73 @@ +/* --------------------------------------------------------------------------- +** +** FifoSource.h +** +** live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_FIFO_SOURCE_H +#define ZM_RTSP_SERVER_FIFO_SOURCE_H + +#include "zm_buffer.h" +#include "zm_config.h" +#include "zm_ffmpeg.h" +#include "zm_define.h" +#include "zm_rtsp_server_frame.h" +#include +#include +#include +#include + +#if HAVE_RTSP_SERVER +#include "xop/RtspServer.h" + +class ZoneMinderFifoSource { + + public: + + void Stop() { + stop_ = true; + condition_.notify_all(); + }; + + ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ); + virtual ~ZoneMinderFifoSource(); + + protected: + void ReadRun(); + void WriteRun(); + + int getNextFrame(); + virtual void PushFrame(const uint8_t *data, size_t size, int64_t pts) = 0; + // split packet in frames + virtual std::list< std::pair > splitFrames(unsigned char* frame, size_t &frameSize); + virtual unsigned char *extractFrame(unsigned char *data, size_t& size, size_t& outsize); + + protected: + + std::mutex mutex_; + std::condition_variable condition_; + + std::thread read_thread_; + std::thread write_thread_; + std::atomic stop_; + + std::shared_ptr& m_rtspServer; + xop::MediaSessionId m_sessionId; + xop::MediaChannelId m_channelId; + std::string m_fifo; + int m_fd; + Buffer m_buffer; + AVRational m_timeBase; + std::queue m_nalQueue; + int m_hType; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_FIFO_SOURCE_H diff --git a/src/zm_rtsp_server_fifo_video_source.cpp b/src/zm_rtsp_server_fifo_video_source.cpp new file mode 100644 index 000000000..df8c32955 --- /dev/null +++ b/src/zm_rtsp_server_fifo_video_source.cpp @@ -0,0 +1,31 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** +** ZoneMinder Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_video_source.h" + +#if HAVE_RTSP_SERVER +ZoneMinderFifoVideoSource::ZoneMinderFifoVideoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ) : + ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo) +{ + m_timeBase = {1, 90000}; +} + +void ZoneMinderFifoVideoSource::PushFrame(const uint8_t *data, size_t size, int64_t pts) { + xop::AVFrame frame(data, size); + frame.timestamp = av_rescale_q(pts, AV_TIME_BASE_Q, m_timeBase); + m_rtspServer->PushFrame(m_sessionId, m_channelId, frame); +} + +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_video_source.h b/src/zm_rtsp_server_fifo_video_source.h new file mode 100644 index 000000000..765b79092 --- /dev/null +++ b/src/zm_rtsp_server_fifo_video_source.h @@ -0,0 +1,39 @@ +/* --------------------------------------------------------------------------- +** +** FifoSource.h +** +** live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_FIFO_VIDEO_SOURCE_H +#define ZM_RTSP_SERVER_FIFO_VIDEO_SOURCE_H + +#include "zm_rtsp_server_fifo_source.h" + +#if HAVE_RTSP_SERVER + +class ZoneMinderFifoVideoSource: public ZoneMinderFifoSource { + + public: + int getWidth() { return m_width; }; + int getHeight() { return m_height; }; + int setWidth(int width) { return m_width=width; }; + int setHeight(int height) { return m_height=height; }; + + ZoneMinderFifoVideoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + std::string fifo + ); + protected: + void PushFrame(const uint8_t *data, size_t size, int64_t pts) override; + + protected: + int m_width; + int m_height; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_FIFO_VIDEO_SOURCE_H diff --git a/src/zm_rtsp_server_frame.h b/src/zm_rtsp_server_frame.h new file mode 100644 index 000000000..363ac7995 --- /dev/null +++ b/src/zm_rtsp_server_frame.h @@ -0,0 +1,79 @@ +#ifndef ZM_RTSP_SERVER_FRAME_H +#define ZM_RTSP_SERVER_FRAME_H + +#include "zm_config.h" +#include "zm_logger.h" +#include +#include + +#if HAVE_RTSP_SERVER +// --------------------------------- +// Captured frame +// --------------------------------- +const char H264marker[] = {0,0,0,1}; +const char H264shortmarker[] = {0,0,1}; + +class NAL_Frame { + public: + NAL_Frame(unsigned char * buffer, size_t size, int64 pts) : + m_buffer(nullptr), + m_size(size), + m_pts(pts), + m_ref_count(1) { + m_buffer = new unsigned char[m_size]; + if (buffer) { + memcpy(m_buffer, buffer, m_size); + } + }; + NAL_Frame& operator=(const NAL_Frame&); + ~NAL_Frame() { + delete[] m_buffer; + m_buffer = nullptr; + }; + unsigned char *buffer() const { return m_buffer; }; + // The buffer has a 32bit nal size value at the front, so if we want the nal, it's + // the address of the buffer plus 4 bytes. + unsigned char *nal() const { return m_buffer+4; }; + size_t size() const { return m_size; }; + size_t size(size_t new_size) { m_size=new_size; return m_size; }; + size_t nal_size() const { return m_size-4; }; + int64_t pts() const { return m_pts; }; + bool check() const { + // Look for marker at beginning + unsigned char *marker = (unsigned char*)memmem(m_buffer, sizeof(H264marker), H264marker, sizeof(H264marker)); + if ( marker ) { + Debug(1, "marker found at beginning"); + return true; + } else { + marker = (unsigned char*)memmem(m_buffer, m_size, H264marker, sizeof(H264marker)); + if ( marker ) { + Debug(1, "marker not found at beginning"); + return false; + } + } + return false; + } + + void debug() { + if (m_size <= 4) { + Debug(1, "NAL: %zu: %.2x %.2x %.2x %.2x", m_size, + m_buffer[0], m_buffer[1], m_buffer[2], m_buffer[3]); + } else { + Debug(1, "NAL: %zu: %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x ", m_size, + m_buffer[0], m_buffer[1], m_buffer[2], m_buffer[3], + m_buffer[4], m_buffer[5], m_buffer[6], m_buffer[7] + ); + } + } + + private: + unsigned char* m_buffer; + size_t m_size; + public: + int64 m_pts; + private: + int m_ref_count; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_FRAME_H diff --git a/src/zm_rtsp_server_h264_device_source.cpp b/src/zm_rtsp_server_h264_device_source.cpp new file mode 100644 index 000000000..71b641021 --- /dev/null +++ b/src/zm_rtsp_server_h264_device_source.cpp @@ -0,0 +1,241 @@ +/* --------------------------------------------------------------------------- +** +** H264_DeviceSource.cpp +** +** H264 Live555 source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_h264_device_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include "zm_rtsp_server_frame.h" +#include +#include + +#if HAVE_RTSP_SERVER +// live555 +#include + +// --------------------------------- +// H264 ZoneMinder FramedSource +// --------------------------------- +// +H264_ZoneMinderDeviceSource::H264_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker) + : H26X_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker) +{ + // extradata appears to simply be the SPS and PPS NAL's + this->splitFrames( +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + m_stream->codecpar->extradata, m_stream->codecpar->extradata_size +#else + m_stream->codec->extradata, m_stream->codec->extradata_size +#endif + ); +} + +// split packet into frames +std::list< std::pair > H264_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { + switch ( m_frameType & 0x1F ) { + case 7: + Debug(1, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer, size); + break; + case 8: + Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer, size); + break; + case 5: + Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + Debug(1, "Unknown frametype!? %d %d", m_frameType, m_frameType & 0x1F); + break; + } + + if ( !m_sps.empty() && !m_pps.empty() ) { + u_int32_t profile_level_id = 0; + if ( m_sps.size() >= 4 ) profile_level_id = (m_sps[1]<<16)|(m_sps[2]<<8)|m_sps[3]; + + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "profile-level-id=" << std::hex << std::setw(6) << std::setfill('0') << profile_level_id; + os << ";sprop-parameter-sets=" << sps_base64 << "," << pps_base64; + m_auxLine.assign(os.str()); + Debug(1, "auxLine: %s", m_auxLine.c_str()); + + delete [] sps_base64; + delete [] pps_base64; + } + frameList.push_back(std::pair(buffer, size)); + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + return frameList; +} + +H265_ZoneMinderDeviceSource::H265_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker) + : H26X_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker) +{ + // extradata appears to simply be the SPS and PPS NAL's + this->splitFrames( +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + m_stream->codecpar->extradata, m_stream->codecpar->extradata_size +#else + m_stream->codec->extradata, m_stream->codec->extradata_size +#endif + ); +} + +// split packet in frames +std::list< std::pair > +H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { + std::list< std::pair > frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + while ( buffer != nullptr ) { + switch ((m_frameType&0x7E)>>1) { + case 32: + Debug(1, "VPS_Size: %d bufSize %d", size, bufSize); + m_vps.assign((char*)buffer,size); + break; + case 33: + Debug(1, "SPS_Size: %d bufSize %d", size, bufSize); + m_sps.assign((char*)buffer,size); + break; + case 34: + Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); + m_pps.assign((char*)buffer,size); + break; + case 19: + case 20: + Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); + if ( m_repeatConfig && !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { + frameList.push_back(std::pair((unsigned char*)m_vps.c_str(), m_vps.size())); + frameList.push_back(std::pair((unsigned char*)m_sps.c_str(), m_sps.size())); + frameList.push_back(std::pair((unsigned char*)m_pps.c_str(), m_pps.size())); + } + break; + default: + Debug(1, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); + break; + } + + if ( !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { + char* vps_base64 = base64Encode(m_vps.c_str(), m_vps.size()); + char* sps_base64 = base64Encode(m_sps.c_str(), m_sps.size()); + char* pps_base64 = base64Encode(m_pps.c_str(), m_pps.size()); + + std::ostringstream os; + os << "sprop-vps=" << vps_base64; + os << ";sprop-sps=" << sps_base64; + os << ";sprop-pps=" << pps_base64; + m_auxLine.assign(os.str()); + Debug(1, "Assigned auxLine to %s", m_auxLine.c_str()); + + delete [] vps_base64; + delete [] sps_base64; + delete [] pps_base64; + } + frameList.push_back(std::pair(buffer, size)); + + buffer = this->extractFrame(&buffer[size], bufSize, size); + } // end while buffer + if ( bufSize ) { + Debug(1, "%d bytes remaining", bufSize); + } + return frameList; +} // end H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) + +unsigned char * H26X_ZoneMinderDeviceSource::findMarker( + unsigned char *frame, size_t size, size_t &length + ) { + //Debug(1, "findMarker %p %d", frame, size); + unsigned char *start = nullptr; + for ( size_t i = 0; i < size-2; i += 1 ) { + //Debug(1, "%d: %d %d %d", i, frame[i], frame[i+1], frame[i+2]); + if ( (frame[i] == 0) and (frame[i+1]) == 0 and (frame[i+2] == 1) ) { + if ( i and (frame[i-1] == 0) ) { + start = frame + i - 1; + length = sizeof(H264marker); + } else { + start = frame + i; + length = sizeof(H264shortmarker); + } + break; + } + } + return start; +} + +// extract a frame +unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { + unsigned char *outFrame = nullptr; + Debug(1, "ExtractFrame: %p %d", frame, size); + outsize = 0; + size_t markerLength = 0; + size_t endMarkerLength = 0; + m_frameType = 0; + unsigned char *startFrame = nullptr; + if ( size >= 3 ) + startFrame = this->findMarker(frame, size, markerLength); + if ( startFrame != nullptr ) { + Debug(1, "startFrame: %p marker Length %d", startFrame, markerLength); + m_frameType = startFrame[markerLength]; + + int remainingSize = size-(startFrame-frame+markerLength); + unsigned char *endFrame = nullptr; + if ( remainingSize > 3 ) { + endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength); + } + Debug(1, "endFrame: %p marker Length %d, remaining size %d", endFrame, endMarkerLength, remainingSize); + + if ( m_keepMarker ) { + size -= startFrame-frame; + outFrame = startFrame; + } else { + size -= startFrame-frame+markerLength; + outFrame = &startFrame[markerLength]; + } + + if ( endFrame != nullptr ) { + outsize = endFrame - outFrame; + } else { + outsize = size; + } + size -= outsize; + Debug(1, "Have frame type: %d size %d, keepmarker %d", m_frameType, outsize, m_keepMarker); + } else if ( size >= sizeof(H264shortmarker) ) { + Info("No marker found"); + } + + return outFrame; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_h264_device_source.h b/src/zm_rtsp_server_h264_device_source.h new file mode 100644 index 000000000..5c01717c5 --- /dev/null +++ b/src/zm_rtsp_server_h264_device_source.h @@ -0,0 +1,104 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** H264_ZoneMinderDeviceSource.h +** +** H264 ZoneMinder live555 source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_H264_DEVICE_SOURCE_H +#define ZM_RTSP_H264_DEVICE_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_device_source.h" + +// --------------------------------- +// H264 ZoneMinder FramedSource +// --------------------------------- +#if HAVE_RTSP_SERVER +class H26X_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { + protected: + H26X_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker) + : + ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize), + m_repeatConfig(repeatConfig), + m_keepMarker(keepMarker), + m_frameType(0) { } + + virtual ~H26X_ZoneMinderDeviceSource() {} + + virtual unsigned char* extractFrame(unsigned char* frame, size_t& size, size_t& outsize); + virtual unsigned char* findMarker(unsigned char *frame, size_t size, size_t &length); + + protected: + std::string m_sps; + std::string m_pps; + bool m_repeatConfig; + bool m_keepMarker; + int m_frameType; +}; + +class H264_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource { + public: + static H264_ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker) { + return new H264_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker); + } + + protected: + H264_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker); + + // overide ZoneMinderDeviceSource + virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); +}; + +class H265_ZoneMinderDeviceSource : public H26X_ZoneMinderDeviceSource { + public: + static H265_ZoneMinderDeviceSource* createNew( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker) { + return new H265_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize, repeatConfig, keepMarker); + } + + protected: + H265_ZoneMinderDeviceSource( + UsageEnvironment& env, + std::shared_ptr monitor, + AVStream *stream, + unsigned int queueSize, + bool repeatConfig, + bool keepMarker); + + // overide ZoneMinderDeviceSource + virtual std::list< std::pair > splitFrames(unsigned char* frame, unsigned frameSize); + + protected: + std::string m_vps; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_H264_DEVICE_SOURCE_H diff --git a/src/zm_rtsp_server_server_media_subsession.cpp b/src/zm_rtsp_server_server_media_subsession.cpp new file mode 100644 index 000000000..141e8dad1 --- /dev/null +++ b/src/zm_rtsp_server_server_media_subsession.cpp @@ -0,0 +1,108 @@ +/* --------------------------------------------------------------------------- +** +** ServerMediaSubsession.cpp +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_server_media_subsession.h" + +#include "zm_config.h" +#include "zm_rtsp_server_adts_source.h" +#include "zm_rtsp_server_adts_fifo_source.h" +#include + +#if HAVE_RTSP_SERVER +// --------------------------------- +// BaseServerMediaSubsession +// --------------------------------- +FramedSource* BaseServerMediaSubsession::createSource( + UsageEnvironment& env, + FramedSource* inputSource, + const std::string& format) +{ + FramedSource* source = nullptr; + if (format == "video/MP2T") { + source = MPEG2TransportStreamFramer::createNew(env, inputSource); + } else if (format == "video/H264") { + source = H264VideoStreamDiscreteFramer::createNew(env, inputSource + /*Boolean includeStartCodeInOutput, Boolean insertAccessUnitDelimiters*/ + ); + } +#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400 + else if (format == "video/H265") { + source = H265VideoStreamDiscreteFramer::createNew(env, inputSource); + } +#endif +#if 0 + else if (format == "video/JPEG") { + source = MJPEGVideoSource::createNew(env, inputSource); + } +#endif + else { + source = inputSource; + } + return source; +} + +/* source is generally a replica */ +RTPSink* BaseServerMediaSubsession::createSink( + UsageEnvironment& env, + Groupsock* rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + const std::string& format, + FramedSource *source + ) { + + RTPSink* sink = nullptr; + if (format == "video/MP2T") { + sink = SimpleRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, 90000, "video", "MP2T", 1, true, false); + } else if (format == "video/H264") { + sink = H264VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (format == "video/VP8") { + sink = VP8VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } +#if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400 + else if (format == "video/VP9") { + sink = VP9VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } else if (format == "video/H265") { + sink = H265VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); +#endif + } else if (format == "audio/AAC") { + ADTS_ZoneMinderFifoSource *adts_source = (ADTS_ZoneMinderFifoSource *)(m_replicator->inputSource()); + sink = MPEG4GenericRTPSink::createNew(env, rtpGroupsock, + rtpPayloadTypeIfDynamic, + adts_source->getFrequency(), + "audio", "AAC-hbr", + adts_source->configStr(), + adts_source->getChannels() + ); + } else { + Error("unknown format"); + } +#if 0 + else if (format == "video/JPEG") { + sink = JPEGVideoRTPSink::createNew (env, rtpGroupsock); + } +#endif + return sink; +} + +char const* BaseServerMediaSubsession::getAuxLine( + ZoneMinderFifoSource* source, + unsigned char rtpPayloadType + ) { + const char* auxLine = nullptr; + if (source) { + std::ostringstream os; + os << "a=fmtp:" << int(rtpPayloadType) << " "; + os << source->getAuxLine(); + //os << "\r\n"; + auxLine = strdup(os.str().c_str()); + Debug(1, "BaseServerMediaSubsession::auxLine: %s", auxLine); + } else { + Error("No source auxLine:"); + return ""; + } + return auxLine; +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_server_media_subsession.h b/src/zm_rtsp_server_server_media_subsession.h new file mode 100644 index 000000000..d3780cd6e --- /dev/null +++ b/src/zm_rtsp_server_server_media_subsession.h @@ -0,0 +1,48 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.h +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H +#define ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" +#include + +#if HAVE_RTSP_SERVER +#include + +class ZoneMinderDeviceSource; + +class BaseServerMediaSubsession { + public: + BaseServerMediaSubsession(StreamReplicator* replicator): + m_replicator(replicator) {}; + + FramedSource* createSource( + UsageEnvironment& env, + FramedSource * videoES, + const std::string& format); + + RTPSink * createSink( + UsageEnvironment& env, + Groupsock * rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + const std::string& format, + FramedSource *source); + + char const* getAuxLine( + ZoneMinderFifoSource* source, + unsigned char rtpPayloadType); + + protected: + StreamReplicator* m_replicator; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H diff --git a/src/zm_rtsp_server_thread.cpp b/src/zm_rtsp_server_thread.cpp new file mode 100644 index 000000000..cbe7b905b --- /dev/null +++ b/src/zm_rtsp_server_thread.cpp @@ -0,0 +1,128 @@ +#include "zm_rtsp_server_thread.h" + +#include "zm_config.h" +#include "zm_logger.h" + +#if HAVE_RTSP_SERVER + +RTSPServerThread::RTSPServerThread(int p_port) : + terminate_(false), scheduler_watch_var_(0), port(p_port) +{ + //unsigned short rtsp_over_http_port = 0; + //const char *realm = "ZoneMinder"; + // + eventLoop = std::make_shared(); + rtspServer = xop::RtspServer::Create(eventLoop.get()); + + if ( rtspServer == nullptr ) { + Fatal("Failed to create rtspServer"); + return; + } + + thread_ = std::thread(&RTSPServerThread::Run, this); +} + +RTSPServerThread::~RTSPServerThread() { + Stop(); + if (thread_.joinable()) + thread_.join(); + +} + +void RTSPServerThread::Run() { + Debug(1, "RTSPServerThread::Run()"); + if (rtspServer) { + while (!scheduler_watch_var_) { + //if (clients > 0) { + sleep(1); + //} + } + } + Debug(1, "RTSPServerThread::done()"); +} + +int RTSPServerThread::Start() { + return rtspServer->Start(std::string("0.0.0.0"), port); +} + +void RTSPServerThread::Stop() { + Debug(1, "RTSPServerThread::stop()"); + terminate_ = true; + + { + std::lock_guard lck(scheduler_watch_var_mutex_); + scheduler_watch_var_ = 1; + } + + for ( std::list::iterator it = sources.begin(); it != sources.end(); ++it ) { + Debug(1, "RTSPServerThread::stopping source"); + (*it)->Stop(); + } + while ( sources.size() ) { + Debug(1, "RTSPServerThread::stop closing source"); + ZoneMinderFifoSource *source = sources.front(); + sources.pop_front(); + delete source; + } +} + +xop::MediaSession *RTSPServerThread::addSession(std::string &streamname) { + + xop::MediaSession *session = xop::MediaSession::CreateNew(streamname); + if (session) { + session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port){ + Debug(1, "RTSP client connect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port); +}); + + session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port) { + Debug(1, "RTSP client disconnect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port); +}); + + rtspServer->AddSession(session); + //char *url = rtspServer->rtspURL(session); + //Debug(1, "url is %s for stream %s", url, streamname.c_str()); + //delete[] url; + } + return session; +} + +void RTSPServerThread::removeSession(xop::MediaSession *session) { + //rtspServer->removeServerMediaSession(session); +} + +ZoneMinderFifoSource *RTSPServerThread::addFifo( + xop::MediaSession *session, + std::string fifo) { + if (!rtspServer) return nullptr; + + ZoneMinderFifoSource *source = nullptr; + + if (!fifo.empty()) { + std::string rtpFormat; + if (std::string::npos != fifo.find("h264")) { + rtpFormat = "video/H264"; + session->AddSource(xop::channel_0, xop::H264Source::CreateNew()); + source = new ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, fifo); + } else if ( + std::string::npos != fifo.find("hevc") + or + std::string::npos != fifo.find("h265")) { + rtpFormat = "video/H265"; + session->AddSource(xop::channel_0, xop::H265Source::CreateNew()); + source = new ZoneMinderFifoSource(rtspServer, session->GetMediaSessionId(), xop::channel_0, fifo); + } else if (std::string::npos != fifo.find("aac")) { + Debug(1, "ADTS source %p", source); + } else { + Warning("Unknown format in %s", fifo.c_str()); + } + if (source == nullptr) { + Error("Unable to create source"); + } + sources.push_back(source); + } else { + Debug(1, "Not Adding stream as fifo was empty"); + } + return source; +} // end void addFifo + +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_thread.h b/src/zm_rtsp_server_thread.h new file mode 100644 index 000000000..393203831 --- /dev/null +++ b/src/zm_rtsp_server_thread.h @@ -0,0 +1,45 @@ +#ifndef ZM_RTSP_SERVER_THREAD_H +#define ZM_RTSP_SERVER_THREAD_H + +#include "zm_config.h" +#include "zm_ffmpeg.h" +#include "xop/RtspServer.h" + +#include "zm_rtsp_server_fifo_source.h" +#include +#include +#include + +#if HAVE_RTSP_SERVER + +class Monitor; + +class RTSPServerThread { + private: + std::shared_ptr monitor_; + + std::thread thread_; + std::atomic terminate_; + std::mutex scheduler_watch_var_mutex_; + char scheduler_watch_var_; + + std::shared_ptr eventLoop; + std::shared_ptr rtspServer; + + std::list sources; + int port; + + public: + explicit RTSPServerThread(int port); + ~RTSPServerThread(); + xop::MediaSession *addSession(std::string &streamname); + void removeSession(xop::MediaSession *sms); + ZoneMinderFifoSource *addFifo(xop::MediaSession *sms, std::string fifo); + void Run(); + void Stop(); + int Start(); + bool IsStopped() const { return terminate_; }; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_THREAD_H diff --git a/src/zm_rtsp_server_unicast_server_media_subsession.cpp b/src/zm_rtsp_server_unicast_server_media_subsession.cpp new file mode 100644 index 000000000..2376748d5 --- /dev/null +++ b/src/zm_rtsp_server_unicast_server_media_subsession.cpp @@ -0,0 +1,52 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.cpp +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_unicast_server_media_subsession.h" + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" + +#if HAVE_RTSP_SERVER +// ----------------------------------------- +// ServerMediaSubsession for Unicast +// ----------------------------------------- +UnicastServerMediaSubsession* UnicastServerMediaSubsession::createNew( + UsageEnvironment& env, + StreamReplicator* replicator, + //FramedSource *source, + const std::string& format + ) { + return new UnicastServerMediaSubsession(env, replicator, format); + //return new UnicastServerMediaSubsession(env, replicator, source, format); +} + +FramedSource* UnicastServerMediaSubsession::createNewStreamSource( + unsigned clientSessionId, + unsigned& estBitrate + ) { + FramedSource* replica = m_replicator->createStreamReplica(); + return createSource(envir(), replica, m_format); +} + +RTPSink* UnicastServerMediaSubsession::createNewRTPSink( + Groupsock* rtpGroupsock, + unsigned char rtpPayloadTypeIfDynamic, + FramedSource* inputSource + ) { + return createSink(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, m_format, inputSource); +} + +char const* UnicastServerMediaSubsession::getAuxSDPLine( + RTPSink* rtpSink, FramedSource* inputSource + ) { + return this->getAuxLine( + dynamic_cast(m_replicator->inputSource()), + rtpSink->rtpPayloadType()); +} +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_unicast_server_media_subsession.h b/src/zm_rtsp_server_unicast_server_media_subsession.h new file mode 100644 index 000000000..d10e3abf1 --- /dev/null +++ b/src/zm_rtsp_server_unicast_server_media_subsession.h @@ -0,0 +1,51 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** ServerMediaSubsession.h +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H +#define ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H + +#include "zm_config.h" +#include "zm_rtsp_server_server_media_subsession.h" + +// ----------------------------------------- +// ServerMediaSubsession for Unicast +// ----------------------------------------- +#if HAVE_RTSP_SERVER +class UnicastServerMediaSubsession : + public OnDemandServerMediaSubsession, + public BaseServerMediaSubsession +{ + public: + static UnicastServerMediaSubsession* createNew( + UsageEnvironment& env, + StreamReplicator* replicator, + const std::string& format); + + protected: + UnicastServerMediaSubsession( + UsageEnvironment& env, + StreamReplicator* replicator, + const std::string& format) + : + OnDemandServerMediaSubsession(env, true + /* Boolean reuseFirstSource, portNumBits initialPortNum=6970, Boolean multiplexRTCPWithRTP=False */ + ), + BaseServerMediaSubsession(replicator), + m_format(format) {}; + + virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate); + virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource); + virtual char const* getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource); + + protected: + const std::string m_format; +}; +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_SERVER_UNICAST_SERVER_MEDIA_SUBSESSION_H diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index de48e169d..63bb8d9bd 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -17,12 +17,14 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" +#include "zm_sdp.h" + +#include "zm_config.h" +#include "zm_exception.h" +#include "zm_logger.h" #if HAVE_LIBAVFORMAT -#include "zm_sdp.h" - #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = { { 0, "PCMU", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_MULAW, 8000, 1 }, @@ -105,7 +107,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : mTtl( 16 ), mNoAddresses( 0 ) { - StringVector tokens = split(connInfo, " "); + StringVector tokens = Split(connInfo, " "); if ( tokens.size() < 3 ) throw Exception( "Unable to parse SDP connection info from '"+connInfo+"'" ); mNetworkType = tokens[0]; @@ -114,7 +116,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : mAddressType = tokens[1]; if ( mAddressType != "IP4" && mAddressType != "IP6" ) throw Exception( "Invalid SDP address type '"+mAddressType+"' in connection info '"+connInfo+"'" ); - StringVector addressTokens = split( tokens[2], "/" ); + StringVector addressTokens = Split(tokens[2], "/"); if ( addressTokens.size() < 1 ) throw Exception( "Invalid SDP address '"+tokens[2]+"' in connection info '"+connInfo+"'" ); mAddress = addressTokens[0]; @@ -127,7 +129,7 @@ SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) : SessionDescriptor::BandInfo::BandInfo( const std::string &bandInfo ) : mValue( 0 ) { - StringVector tokens = split( bandInfo, ":" ); + StringVector tokens = Split(bandInfo, ":"); if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP bandwidth info from '"+bandInfo+"'" ); mType = tokens[0]; @@ -163,7 +165,7 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string { MediaDescriptor *currMedia = nullptr; - StringVector lines = split( sdp, "\r\n" ); + StringVector lines = Split(sdp, "\r\n"); for ( StringVector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter ) { std::string line = *iter; if ( line.empty() ) @@ -206,7 +208,7 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string case 'a' : { mAttributes.push_back( line ); - StringVector tokens = split( line, ":", 2 ); + StringVector tokens = Split(line, ":", 2); std::string attrName = tokens[0]; if ( currMedia ) { if ( attrName == "control" ) { @@ -218,14 +220,12 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=rtpmap:96 MP4V-ES/90000 if ( tokens.size() < 2 ) throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" ); - StringVector attrTokens = split( tokens[1], " " ); + StringVector attrTokens = Split(tokens[1], " "); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) ); - std::string payloadDesc = attrTokens[1]; - //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { - StringVector payloadTokens = split( attrTokens[1], "/" ); + StringVector payloadTokens = Split(attrTokens[1], "/"); std::string payloadDesc = payloadTokens[0]; int payloadClock = atoi(payloadTokens[1].c_str()); currMedia->setPayloadDesc( payloadDesc ); @@ -235,13 +235,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=framesize:96 320-240 if ( tokens.size() < 2 ) throw Exception("Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'"); - StringVector attrTokens = split(tokens[1], " "); + StringVector attrTokens = Split(tokens[1], " "); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception( stringtf("Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str())); //currMedia->setPayloadType( payloadType ); - StringVector sizeTokens = split(attrTokens[1], "-"); + StringVector sizeTokens = Split(attrTokens[1], "-"); int width = atoi(sizeTokens[0].c_str()); int height = atoi(sizeTokens[1].c_str()); currMedia->setFrameSize(width, height); @@ -255,16 +255,16 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string // a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F if ( tokens.size() < 2 ) throw Exception("Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'"); - StringVector attrTokens = split(tokens[1], " ", 2); + StringVector attrTokens = Split(tokens[1], " ", 2); int payloadType = atoi(attrTokens[0].c_str()); if ( payloadType != currMedia->getPayloadType() ) throw Exception(stringtf("Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str())); //currMedia->setPayloadType( payloadType ); if ( attrTokens.size() > 1 ) { - StringVector attr2Tokens = split( attrTokens[1], "; " ); + StringVector attr2Tokens = Split(attrTokens[1], "; "); for ( unsigned int i = 0; i < attr2Tokens.size(); i++ ) { - StringVector attr3Tokens = split( attr2Tokens[i], "=" ); + StringVector attr3Tokens = Split(attr2Tokens[i], "="); //Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() ); if ( attr3Tokens[0] == "profile-level-id" ) { } else if ( attr3Tokens[0] == "config" ) { @@ -274,7 +274,9 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string Debug(4, "sprop-parameter-sets value %s", c); currMedia->setSprops(std::string(c)); } else { - Debug( 3, "Ignoring SDP fmtp attribute '%s' for media '%s'", attr3Tokens[0].c_str(), currMedia->getType().c_str() ) + Debug(3, "Ignoring SDP fmtp attribute '%s' for media '%s'", + attr3Tokens[0].c_str(), + currMedia->getType().c_str()); } } } @@ -292,13 +294,13 @@ SessionDescriptor::SessionDescriptor( const std::string &url, const std::string } case 'm' : { - StringVector tokens = split(line, " "); + StringVector tokens = Split(line, " "); if ( tokens.size() < 4 ) throw Exception("Can't parse SDP media description '"+line+"'"); std::string mediaType = tokens[0]; if ( mediaType != "audio" && mediaType != "video" && mediaType != "application" ) throw Exception("Unsupported media type '"+mediaType+"' in SDP media attribute '"+line+"'"); - StringVector portTokens = split(tokens[1], "/"); + StringVector portTokens = Split(tokens[1], "/"); int mediaPort = atoi(portTokens[0].c_str()); int mediaNumPorts = 1; if ( portTokens.size() > 1 ) @@ -350,8 +352,6 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) AVCodecContext *codec_context = avcodec_alloc_context3(nullptr); - avcodec_parameters_to_context(codec_context, stream->codecpar); - stream->codec = codec_context; #else AVCodecContext *codec_context = stream->codec; #endif @@ -376,47 +376,45 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { #endif else Warning("Unknown media_type %s", type.c_str()); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - stream->codecpar->codec_type = codec_context->codec_type; -#endif #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) std::string codec_name; #endif if ( mediaDesc->getPayloadType() < PAYLOAD_TYPE_DYNAMIC ) { // Look in static table - for ( unsigned int i = 0; i < (sizeof(smStaticPayloads)/sizeof(*smStaticPayloads)); i++ ) { - if ( smStaticPayloads[i].payloadType == mediaDesc->getPayloadType() ) { - Debug( 1, "Got static payload type %d, %s", smStaticPayloads[i].payloadType, smStaticPayloads[i].payloadName ); + for ( unsigned int j = 0; j < (sizeof(smStaticPayloads)/sizeof(*smStaticPayloads)); j++ ) { + if ( smStaticPayloads[j].payloadType == mediaDesc->getPayloadType() ) { + Debug( 1, "Got static payload type %d, %s", smStaticPayloads[j].payloadType, smStaticPayloads[j].payloadName ); #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - codec_name = std::string(smStaticPayloads[i].payloadName); + codec_name = std::string(smStaticPayloads[j].payloadName); #else - strncpy(codec_context->codec_name, smStaticPayloads[i].payloadName, sizeof(codec_context->codec_name)); + strncpy(codec_context->codec_name, smStaticPayloads[j].payloadName, sizeof(codec_context->codec_name)); #endif - codec_context->codec_type = smStaticPayloads[i].codecType; - codec_context->codec_id = smStaticPayloads[i].codecId; - codec_context->sample_rate = smStaticPayloads[i].clockRate; + codec_context->codec_type = smStaticPayloads[j].codecType; + codec_context->codec_id = smStaticPayloads[j].codecId; + codec_context->sample_rate = smStaticPayloads[j].clockRate; break; } } } else { // Look in dynamic table - for ( unsigned int i = 0; i < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); i++ ) { - if ( smDynamicPayloads[i].payloadName == mediaDesc->getPayloadDesc() ) { - Debug(1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[i].payloadName); + for ( unsigned int j = 0; j < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); j++ ) { + if ( smDynamicPayloads[j].payloadName == mediaDesc->getPayloadDesc() ) { + Debug(1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[j].payloadName); #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - codec_name = std::string(smStaticPayloads[i].payloadName); + codec_name = std::string(smStaticPayloads[j].payloadName); #else - strncpy(codec_context->codec_name, smDynamicPayloads[i].payloadName, sizeof(codec_context->codec_name)); + strncpy(codec_context->codec_name, smDynamicPayloads[j].payloadName, sizeof(codec_context->codec_name)); #endif - codec_context->codec_type = smDynamicPayloads[i].codecType; - codec_context->codec_id = smDynamicPayloads[i].codecId; + codec_context->codec_type = smDynamicPayloads[j].codecType; + codec_context->codec_id = smDynamicPayloads[j].codecId; codec_context->sample_rate = mediaDesc->getClock(); break; } } } /// end if static or dynamic + #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) if ( codec_name.empty() ) #else @@ -425,7 +423,6 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { { Warning( "Can't find payload details for %s payload type %d, name %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() ); - //return( 0 ); } if ( mediaDesc->getWidth() ) codec_context->width = mediaDesc->getWidth(); @@ -439,7 +436,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { strcpy(pvalue, mediaDesc->getSprops().c_str()); - while (*value) { + while ( *value ) { char base64packet[1024]; uint8_t decoded_packet[1024]; uint32_t packet_size; @@ -454,9 +451,9 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { if ( *value == ',' ) value++; - packet_size= av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); + packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); Hexdump(4, (char *)decoded_packet, packet_size); - if (packet_size) { + if ( packet_size ) { uint8_t *dest = (uint8_t *)av_malloc(packet_size + sizeof(start_sequence) + codec_context->extradata_size + @@ -493,7 +490,10 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { } } } - } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_parameters_from_context(stream->codecpar, codec_context); +#endif + } // end foreach mediaList return formatContext; } diff --git a/src/zm_sdp.h b/src/zm_sdp.h index 403c8a740..e99c12920 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -20,14 +20,8 @@ #ifndef ZM_SDP_H #define ZM_SDP_H -#include "zm.h" - -#include "zm_utils.h" -#include "zm_exception.h" #include "zm_ffmpeg.h" - -#include - +#include "zm_utils.h" #include #include @@ -120,7 +114,7 @@ public: { return( mTransport ); } - const int getPayloadType() const + int getPayloadType() const { return( mPayloadType ); } @@ -142,7 +136,7 @@ public: mControlUrl = controlUrl; } - const int getClock() const { + int getClock() const { return( mClock ); } void setClock( int clock ) { @@ -163,10 +157,10 @@ public: void setSprops(const std::string &props) { mSprops = props; } - const std::string getSprops() const { + std::string getSprops() const { return ( mSprops ); } - const double getFrameRate() const { + double getFrameRate() const { return( mFrameRate ); } void setFrameRate( double frameRate ) { diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index 81012c07e..9a92b5eac 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -17,27 +17,24 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include "zm.h" #include "zm_signal.h" -#include -#include -#include +#include "zm.h" +#include "zm_logger.h" +#include #define TRACE_SIZE 16 bool zm_reload = false; bool zm_terminate = false; -RETSIGTYPE zm_hup_handler(int signal) -{ +RETSIGTYPE zm_hup_handler(int signal) { // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. //Info("Got signal %d (%s), reloading", signal, strsignal(signal)); zm_reload = true; } -RETSIGTYPE zm_term_handler(int signal) -{ +RETSIGTYPE zm_term_handler(int signal) { // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. //Info("Got signal %d (%s), exiting", signal, strsignal(signal)); zm_terminate = true; @@ -55,8 +52,7 @@ RETSIGTYPE zm_die_handler(int signal) #if ( HAVE_SIGINFO_T && HAVE_UCONTEXT_T ) void *ip = nullptr; void *cr2 = nullptr; - if (info && context) { - + if ( info && context ) { Debug(1, "Signal information: number %d code %d errno %d pid %d uid %d status %d", signal, info->si_code, info->si_errno, info->si_pid, @@ -79,7 +75,7 @@ RETSIGTYPE zm_die_handler(int signal) #endif // defined(__x86_64__) // Print the signal address and instruction pointer if available - if (ip) { + if ( ip ) { Error("Signal address is %p, from %p", cr2, ip); } else { Error("Signal address is %p, no instruction pointer", cr2); @@ -108,15 +104,13 @@ RETSIGTYPE zm_die_handler(int signal) } free(messages); - Info("Backtrace complete, please execute the following command for more information"); - Info(cmd); + Info("Backtrace complete, please execute the following command for more information: %s", cmd); #endif // ( !defined(ZM_NO_CRASHTRACE) && HAVE_DECL_BACKTRACE && HAVE_DECL_BACKTRACE_SYMBOLS ) #endif // (defined(__i386__) || defined(__x86_64__) exit(signal); } -void zmSetHupHandler(SigHandler * handler) -{ +void zmSetHupHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -127,8 +121,7 @@ void zmSetHupHandler(SigHandler * handler) sigaction(SIGHUP, &action, &old_action); } -void zmSetTermHandler(SigHandler * handler) -{ +void zmSetTermHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -141,8 +134,7 @@ void zmSetTermHandler(SigHandler * handler) sigaction(SIGQUIT, &action, &old_action); } -void zmSetDieHandler(SigHandler * handler) -{ +void zmSetDieHandler(SigHandler * handler) { sigset_t block_set; sigemptyset(&block_set); struct sigaction action, old_action; @@ -163,19 +155,16 @@ void zmSetDieHandler(SigHandler * handler) sigaction(SIGFPE, &action, &old_action); } -void zmSetDefaultHupHandler() -{ +void zmSetDefaultHupHandler() { zmSetHupHandler((SigHandler *) zm_hup_handler); } -void zmSetDefaultTermHandler() -{ +void zmSetDefaultTermHandler() { zmSetTermHandler((SigHandler *) zm_term_handler); } -void zmSetDefaultDieHandler() -{ - if (config.dump_cores) { +void zmSetDefaultDieHandler() { + if ( config.dump_cores ) { // Do nothing } else { zmSetDieHandler((SigHandler *) zm_die_handler); diff --git a/src/zm_signal.h b/src/zm_signal.h index 89a4b408a..27fa2e766 100644 --- a/src/zm_signal.h +++ b/src/zm_signal.h @@ -20,7 +20,8 @@ #ifndef ZM_SIGNAL_H #define ZM_SIGNAL_H -#include +#include "zm_config.h" +#include #if HAVE_EXECINFO_H #include @@ -29,9 +30,6 @@ #include #endif - -#include "zm.h" - typedef RETSIGTYPE (SigHandler)( int ); extern bool zm_reload; diff --git a/src/zm_storage.cpp b/src/zm_storage.cpp index 4bba82bab..5edc36e00 100644 --- a/src/zm_storage.cpp +++ b/src/zm_storage.cpp @@ -15,36 +15,33 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm.h" -#include "zm_db.h" +*/ #include "zm_storage.h" -#include -#include -#include +#include "zm_db.h" +#include "zm_logger.h" +#include -Storage::Storage() { +Storage::Storage() : id(0) { Warning("Instantiating default Storage Object. Should not happen."); - id = 0; strcpy(name, "Default"); if ( staticConfig.DIR_EVENTS[0] != '/' ) { // not using an absolute path. Make it one by appending ZM_PATH_WEB - snprintf( path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str( ), staticConfig.DIR_EVENTS.c_str() ); + snprintf(path, sizeof(path), "%s/%s", + staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); } else { - strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1 ); + strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); } scheme = MEDIUM; scheme_str = "Medium"; } -Storage::Storage( MYSQL_ROW &dbrow ) { +Storage::Storage(MYSQL_ROW &dbrow) { unsigned int index = 0; - id = atoi( dbrow[index++] ); - strncpy( name, dbrow[index++], sizeof(name)-1 ); - strncpy( path, dbrow[index++], sizeof(path)-1 ); + id = atoi(dbrow[index++]); + strncpy(name, dbrow[index++], sizeof(name)-1); + strncpy(path, dbrow[index++], sizeof(path)-1); type_str = std::string(dbrow[index++]); scheme_str = std::string(dbrow[index++]); if ( scheme_str == "Deep" ) { @@ -57,16 +54,15 @@ Storage::Storage( MYSQL_ROW &dbrow ) { } /* If a zero or invalid p_id is passed, then the old default path will be assumed. */ -Storage::Storage( unsigned int p_id ) { - id = 0; +Storage::Storage(unsigned int p_id) : id(p_id) { - if ( p_id ) { + if ( id ) { char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%d", p_id); - Debug(2,"Loading Storage for %d using %s", p_id, sql ); + snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id); + Debug(2, "Loading Storage for %u using %s", id, sql); zmDbRow dbrow; if ( !dbrow.fetch(sql) ) { - Error("Unable to load storage area for id %d: %s", p_id, mysql_error(&dbconn)); + Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn)); } else { unsigned int index = 0; id = atoi(dbrow[index++]); @@ -81,17 +77,18 @@ Storage::Storage( unsigned int p_id ) { } else { scheme = SHALLOW; } - Debug(1, "Loaded Storage area %d '%s'", id, this->Name()); + Debug(1, "Loaded Storage area %d '%s'", id, name); } } if ( !id ) { if ( staticConfig.DIR_EVENTS[0] != '/' ) { // not using an absolute path. Make it one by appending ZM_PATH_WEB - snprintf(path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); + snprintf(path, sizeof(path), "%s/%s", + staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str()); } else { strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1); } - Debug(1,"No id passed to Storage constructor. Using default path %s instead", path); + Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); strcpy(name, "Default"); scheme = MEDIUM; scheme_str = "Medium"; diff --git a/src/zm_storage.h b/src/zm_storage.h index f0886c9f3..6ea0bae5a 100644 --- a/src/zm_storage.h +++ b/src/zm_storage.h @@ -15,13 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "zm_db.h" +*/ #ifndef ZM_STORAGE_H #define ZM_STORAGE_H +#include "zm_db.h" +#include + class Storage { public: typedef enum { @@ -47,8 +48,8 @@ public: unsigned int Id() const { return id; } const char *Name() const { return name; } const char *Path() const { return path; } - const Schemes Scheme() const { return scheme; } - const std::string SchemeString() const { return scheme_str; } + Schemes Scheme() const { return scheme; } + std::string SchemeString() const { return scheme_str; } }; #endif // ZM_STORAGE_H diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 05236c3ba..0968c9514 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -17,18 +17,16 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_mpeg.h" -#include "zm_monitor.h" - #include "zm_stream.h" +#include "zm_box.h" +#include "zm_monitor.h" +#include +#include +#include +#include +#include + StreamBase::~StreamBase() { #if HAVE_LIBAVCODEC if ( vid_stream ) { @@ -37,28 +35,28 @@ StreamBase::~StreamBase() { } #endif closeComms(); - if ( monitor ) { - delete monitor; - monitor = NULL; - } } bool StreamBase::loadMonitor(int p_monitor_id) { monitor_id = p_monitor_id; - if ( monitor ) - delete monitor; - if ( !(monitor = Monitor::Load(monitor_id, false, Monitor::QUERY)) ) { + if ( !(monitor or (monitor = Monitor::Load(monitor_id, false, Monitor::QUERY))) ) { Error("Unable to load monitor id %d for streaming", monitor_id); return false; } + if ( monitor->GetFunction() == Monitor::NONE ) { - Error("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id); + Info("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id); return false; } + if ( monitor->isConnected() ) { + monitor->disconnect(); + } + if ( !monitor->connect() ) { Error("Unable to connect to monitor id %d for streaming", monitor_id); + monitor->disconnect(); return false; } @@ -66,24 +64,28 @@ bool StreamBase::loadMonitor(int p_monitor_id) { } bool StreamBase::checkInitialised() { - if ( !monitor ) { + if (!monitor) { Error("Cannot stream, not initialised"); return false; } - if ( monitor->GetFunction() == Monitor::NONE ) { - Error("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id); + if (monitor->GetFunction() == Monitor::NONE) { + Info("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id); return false; } - if ( !monitor->ShmValid() ) { + if (!monitor->ShmValid()) { Error("Monitor shm is not connected"); return false; } + if ((monitor->GetType() == Monitor::FFMPEG) and !monitor->DecodingEnabled() ) { + Debug(1, "Monitor is not decoding."); + return false; + } return true; } void StreamBase::updateFrameRate(double fps) { frame_mod = 1; - if ( (fps < 0) || !fps || isinf(fps) ) { + if ( (fps < 0) || !fps || std::isinf(fps) ) { Debug(1, "Zero or negative fps %f in updateFrameRate. Setting frame_mod=1 and effective_fps=0.0", fps); effective_fps = 0.0; base_fps = 0.0; @@ -124,11 +126,14 @@ bool StreamBase::checkCommandQueue() { processCommand(&msg); return true; } - } else { + } else if ( connkey ) { Warning("No sd in checkCommandQueue, comms not open?"); + } else { + // Perfectly valid if only getting a snapshot + Debug(1, "No sd in checkCommandQueue, comms not open?"); } return false; -} +} // end bool StreamBase::checkCommandQueue() Image *StreamBase::prepareImage(Image *image) { @@ -252,7 +257,8 @@ bool StreamBase::sendTextFrame(const char *frame_text) { monitor->Width(), monitor->Height(), scale, frame_text); Image image(monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder()); - image.Annotate(frame_text, image.centreCoord(frame_text)); + image.Clear(); + image.Annotate(frame_text, image.centreCoord(frame_text, monitor->LabelSize()), monitor->LabelSize()); if ( scale != 100 ) { image.Scale(scale); @@ -353,7 +359,7 @@ void StreamBase::openComms() { // Unlink before bind, in case it already exists unlink(loc_sock_path); if ( sizeof(loc_addr.sun_path) < length ) { - Error("Not enough space %d in loc_addr.sun_path for socket file %s", sizeof(loc_addr.sun_path), loc_sock_path); + Error("Not enough space %zu in loc_addr.sun_path for socket file %s", sizeof(loc_addr.sun_path), loc_sock_path); } strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path)); diff --git a/src/zm_stream.h b/src/zm_stream.h index d906b2de6..6d752689d 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -20,12 +20,12 @@ #ifndef ZM_STREAM_H #define ZM_STREAM_H -#include -#include - -#include "zm.h" +#include "zm_logger.h" #include "zm_mpeg.h" +#include +#include +class Image; class Monitor; #define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) ) @@ -33,7 +33,13 @@ class Monitor; class StreamBase { public: - typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType; + typedef enum { + STREAM_JPEG, + STREAM_RAW, + STREAM_ZIP, + STREAM_SINGLE, + STREAM_MPEG + } StreamType; protected: static const int MAX_STREAM_DELAY = 5; // Seconds @@ -57,12 +63,37 @@ protected: char msg_data[256]; } DataMsg; - typedef enum { MSG_CMD=1, MSG_DATA_WATCH, MSG_DATA_EVENT } MsgType; - typedef enum { CMD_NONE=0, CMD_PAUSE, CMD_PLAY, CMD_STOP, CMD_FASTFWD, CMD_SLOWFWD, CMD_SLOWREV, CMD_FASTREV, CMD_ZOOMIN, CMD_ZOOMOUT, CMD_PAN, CMD_SCALE, CMD_PREV, CMD_NEXT, CMD_SEEK, CMD_VARPLAY, CMD_GET_IMAGE, CMD_QUIT, CMD_QUERY=99 } MsgCommand; + typedef enum { + MSG_CMD=1, + MSG_DATA_WATCH, + MSG_DATA_EVENT + } MsgType; + + typedef enum { + CMD_NONE=0, + CMD_PAUSE, + CMD_PLAY, + CMD_STOP, + CMD_FASTFWD, + CMD_SLOWFWD, + CMD_SLOWREV, + CMD_FASTREV, + CMD_ZOOMIN, + CMD_ZOOMOUT, + CMD_PAN, + CMD_SCALE, + CMD_PREV, + CMD_NEXT, + CMD_SEEK, + CMD_VARPLAY, + CMD_GET_IMAGE, + CMD_QUIT, + CMD_QUERY=99 + } MsgCommand; protected: int monitor_id; - Monitor *monitor; + std::shared_ptr monitor; StreamType type; const char *format; @@ -109,14 +140,13 @@ protected: bool checkInitialised(); void updateFrameRate(double fps); Image *prepareImage(Image *image); - bool sendTextFrame(const char *text); bool checkCommandQueue(); virtual void processCommand(const CmdMsg *msg)=0; public: StreamBase(): monitor_id(0), - monitor(0), + monitor(nullptr), type(DEFAULT_TYPE), format(""), replay_rate(DEFAULT_RATE), @@ -137,7 +167,7 @@ public: lock_fd(0), paused(false), step(0) - { + { memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_addr, 0, sizeof(loc_addr)); memset(&rem_sock_path, 0, sizeof(rem_sock_path)); @@ -152,7 +182,7 @@ public: vid_stream = 0; #endif // HAVE_LIBAVCODEC last_frame_sent = 0.0; - last_frame_timestamp = (struct timeval){0}; + last_frame_timestamp = {}; msg = { 0, { 0 } }; } virtual ~StreamBase(); @@ -188,6 +218,7 @@ public: void setStreamQueue(int p_connkey) { connkey = p_connkey; } + bool sendTextFrame(const char *text); virtual void openComms(); virtual void closeComms(); virtual void runStream()=0; diff --git a/src/zm_swscale.cpp b/src/zm_swscale.cpp index 7afb9521c..b5cf6eded 100644 --- a/src/zm_swscale.cpp +++ b/src/zm_swscale.cpp @@ -15,18 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "zm_ffmpeg.h" -#include "zm_image.h" -#include "zm_rgb.h" +*/ #include "zm_swscale.h" +#include "zm_image.h" +#include "zm_logger.h" + #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL SWScale::SWScale() : gotdefaults(false), swscale_ctx(nullptr), input_avframe(nullptr), output_avframe(nullptr) { - Debug(4,"SWScale object created"); - + Debug(4, "SWScale object created"); } bool SWScale::init() { @@ -68,10 +66,14 @@ SWScale::~SWScale() { swscale_ctx = nullptr; } - Debug(4,"SWScale object destroyed"); + Debug(4, "SWScale object destroyed"); } -int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { +int SWScale::SetDefaults( + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { /* Assign the defaults */ default_input_pf = in_pf; @@ -84,55 +86,105 @@ int SWScale::SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, return 0; } -int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height, unsigned int new_width, unsigned int new_height) { +int SWScale::Convert( + AVFrame *in_frame, + AVFrame *out_frame +) { + + AVPixelFormat format = fix_deprecated_pix_fmt((AVPixelFormat)in_frame->format); + /* Get the context */ + swscale_ctx = sws_getCachedContext(swscale_ctx, + in_frame->width, in_frame->height, format, + out_frame->width, out_frame->height, (AVPixelFormat)out_frame->format, + SWS_FAST_BILINEAR, NULL, NULL, NULL); + if ( swscale_ctx == NULL ) { + Error("Failed getting swscale context"); + return -6; + } + /* Do the conversion */ + if (!sws_scale(swscale_ctx, + in_frame->data, in_frame->linesize, 0, in_frame->height, + out_frame->data, out_frame->linesize)) { + Error("swscale conversion failed"); + return -10; + } + + return 0; +} + +int SWScale::Convert( + const uint8_t* in_buffer, + const size_t in_buffer_size, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height, + unsigned int new_width, + unsigned int new_height + ) { + Debug(1, "Convert: in_buffer %p in_buffer_size %zu out_buffer %p size %zu width %d height %d width %d height %d %d %d", + in_buffer, in_buffer_size, out_buffer, out_buffer_size, width, height, new_width, new_height, + in_pf, out_pf); /* Parameter checking */ - if(in_buffer == nullptr || out_buffer == nullptr) { - Error("NULL Input or output buffer"); + if ( in_buffer == nullptr ) { + Error("NULL Input buffer"); + return -1; + } + if ( out_buffer == nullptr ) { + Error("NULL output buffer"); return -1; } // if(in_pf == 0 || out_pf == 0) { // Error("Invalid input or output pixel formats"); // return -2; // } - if (!width || !height || !new_height || !new_width) { + if ( !width || !height || !new_height || !new_width ) { Error("Invalid width or height"); return -3; } + in_pf = fix_deprecated_pix_fmt(in_pf); + #if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) /* Warn if the input or output pixelformat is not supported */ - if(!sws_isSupportedInput(in_pf)) { - Warning("swscale does not support the input format: %c%c%c%c",(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff)); + if ( !sws_isSupportedInput(in_pf) ) { + Warning("swscale does not support the input format: %c%c%c%c", + (in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff)); } - if(!sws_isSupportedOutput(out_pf)) { - Warning("swscale does not support the output format: %c%c%c%c",(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff)); + if ( !sws_isSupportedOutput(out_pf) ) { + Warning("swscale does not support the output format: %c%c%c%c", + (out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff)); } #endif + int alignment = 1; /* Check the buffer sizes */ -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - size_t insize = av_image_get_buffer_size(in_pf, width, height, 1); -#else - size_t insize = avpicture_get_size(in_pf, width, height); -#endif - if(insize != in_buffer_size) { - Error("The input buffer size does not match the expected size for the input format. Required: %d Available: %d", insize, in_buffer_size); - return -4; + size_t needed_insize = GetBufferSize(in_pf, width, height); + if ( needed_insize > in_buffer_size ) { + Debug(1, + "The input buffer size does not match the expected size for the input format. Required: %zu for %dx%d %d Available: %zu", + needed_insize, + width, + height, + in_pf, + in_buffer_size); } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - size_t outsize = av_image_get_buffer_size(out_pf, new_width, new_height, 1); -#else - size_t outsize = avpicture_get_size(out_pf, new_width, new_height); -#endif - - if(outsize < out_buffer_size) { - Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size); + size_t needed_outsize = GetBufferSize(out_pf, new_width, new_height); + if ( needed_outsize > out_buffer_size ) { + Error("The output buffer is undersized for the output format. Required: %zu Available: %zu", + needed_outsize, + out_buffer_size); return -5; } /* Get the context */ - swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, new_width, new_height, out_pf, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); - if(swscale_ctx == nullptr) { + swscale_ctx = sws_getCachedContext(swscale_ctx, + width, height, in_pf, + new_width, new_height, out_pf, + SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); + if ( swscale_ctx == nullptr ) { Error("Failed getting swscale context"); return -6; } @@ -140,7 +192,7 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint /* Fill in the buffers */ #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, - (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { + (uint8_t*) in_buffer, in_pf, width, height, alignment) <= 0) { #else if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, in_pf, width, height) <= 0) { @@ -150,7 +202,7 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, - out_buffer, out_pf, new_width, new_height, 1) <= 0) { + out_buffer, out_pf, new_width, new_height, alignment) <= 0) { #else if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, new_width, new_height) <= 0) { @@ -160,7 +212,10 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint } /* Do the conversion */ - if(!sws_scale(swscale_ctx, input_avframe->data, input_avframe->linesize, 0, height, output_avframe->data, output_avframe->linesize ) ) { + if ( !sws_scale(swscale_ctx, + input_avframe->data, input_avframe->linesize, + 0, height, + output_avframe->data, output_avframe->linesize) ) { Error("swscale conversion failed"); return -10; } @@ -168,27 +223,42 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint return 0; } -int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { +int SWScale::Convert( + const uint8_t* in_buffer, + const size_t in_buffer_size, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { return Convert(in_buffer, in_buffer_size, out_buffer, out_buffer_size, in_pf, out_pf, width, height, width, height); } -int SWScale::Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height) { - if(img->Width() != width) { - Error("Source image width differs. Source: %d Output: %d",img->Width(), width); +int SWScale::Convert( + const Image* img, + uint8_t* out_buffer, + const size_t out_buffer_size, + enum _AVPIXELFORMAT in_pf, + enum _AVPIXELFORMAT out_pf, + unsigned int width, + unsigned int height) { + if ( img->Width() != width ) { + Error("Source image width differs. Source: %d Output: %d", img->Width(), width); return -12; } - if(img->Height() != height) { - Error("Source image height differs. Source: %d Output: %d",img->Height(), height); + if ( img->Height() != height ) { + Error("Source image height differs. Source: %d Output: %d", img->Height(), height); return -13; } - return Convert(img->Buffer(),img->Size(),out_buffer,out_buffer_size,in_pf,out_pf,width,height); + return Convert(img->Buffer(), img->Size(), out_buffer, out_buffer_size, in_pf, out_pf, width, height); } int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size) { - if(!gotdefaults) { + if ( !gotdefaults ) { Error("Defaults are not set"); return -24; } @@ -198,11 +268,19 @@ int SWScale::ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size) { - if(!gotdefaults) { + if ( !gotdefaults ) { Error("Defaults are not set"); return -24; } return Convert(in_buffer,in_buffer_size,out_buffer,out_buffer_size,default_input_pf,default_output_pf,default_width,default_height); } + +size_t SWScale::GetBufferSize(enum _AVPIXELFORMAT pf, unsigned int width, unsigned int height) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + return av_image_get_buffer_size(pf, width, height, 1); +#else + return outsize = avpicture_get_size(pf, width,height); +#endif +} #endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL diff --git a/src/zm_swscale.h b/src/zm_swscale.h index 4d559b4fc..8e7e1fa19 100644 --- a/src/zm_swscale.h +++ b/src/zm_swscale.h @@ -1,9 +1,11 @@ #ifndef ZM_SWSCALE_H #define ZM_SWSCALE_H -#include "zm_image.h" +#include "zm_config.h" #include "zm_ffmpeg.h" +class Image; + /* SWScale wrapper class to make our life easier and reduce code reuse */ #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { @@ -14,9 +16,11 @@ class SWScale { int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); + int Convert( AVFrame *in_frame, AVFrame *out_frame ); int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height, unsigned int new_width, unsigned int new_height); + static size_t GetBufferSize(enum _AVPIXELFORMAT in_pf, unsigned int width, unsigned int height); protected: bool gotdefaults; diff --git a/src/zm_thread.cpp b/src/zm_thread.cpp deleted file mode 100644 index a4bbe763a..000000000 --- a/src/zm_thread.cpp +++ /dev/null @@ -1,315 +0,0 @@ -// -// ZoneMinder Thread Class Implementation, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#include "zm_thread.h" - -#include "zm_logger.h" -#include "zm_utils.h" - -#include -#include -#include -#include - -struct timespec getTimeout( int secs ) { - struct timespec timeout; - struct timeval temp_timeout; - gettimeofday(&temp_timeout, nullptr); - timeout.tv_sec = temp_timeout.tv_sec + secs; - timeout.tv_nsec = temp_timeout.tv_usec*1000; - return timeout; -} - -struct timespec getTimeout( double secs ) { - struct timespec timeout; - struct timeval temp_timeout; - gettimeofday( &temp_timeout, nullptr ); - timeout.tv_sec = temp_timeout.tv_sec + int(secs); - timeout.tv_nsec = temp_timeout.tv_usec += (long int)(1000000000.0*(secs-int(secs))); - if ( timeout.tv_nsec > 1000000000 ) { - timeout.tv_sec += 1; - timeout.tv_nsec -= 1000000000; - } - return timeout; -} - -Mutex::Mutex() { - if ( pthread_mutex_init(&mMutex, nullptr) < 0 ) - Error("Unable to create pthread mutex: %s", strerror(errno)); -} - -Mutex::~Mutex() { - if ( locked() ) - Warning("Destroying mutex when locked"); - if ( pthread_mutex_destroy(&mMutex) < 0 ) - Error("Unable to destroy pthread mutex: %s", strerror(errno)); -} - -int Mutex::trylock() { - return pthread_mutex_trylock(&mMutex); -} -void Mutex::lock() { - if ( pthread_mutex_lock(&mMutex) < 0 ) - throw ThreadException( stringtf( "Unable to lock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::lock( int secs ) { - struct timespec timeout = getTimeout( secs ); - if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) - throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::lock( double secs ) { - struct timespec timeout = getTimeout( secs ); - if ( pthread_mutex_timedlock( &mMutex, &timeout ) < 0 ) - throw ThreadException( stringtf( "Unable to timedlock pthread mutex: %s", strerror(errno) ) ); -} - -void Mutex::unlock() { - if ( pthread_mutex_unlock( &mMutex ) < 0 ) - throw ThreadException( stringtf( "Unable to unlock pthread mutex: %s", strerror(errno) ) ); -} - -bool Mutex::locked() { - int state = pthread_mutex_trylock( &mMutex ); - if ( state != 0 && state != EBUSY ) - throw ThreadException( stringtf( "Unable to trylock pthread mutex: %s", strerror(errno) ) ); - if ( state != EBUSY ) - unlock(); - return( state == EBUSY ); -} - -RecursiveMutex::RecursiveMutex() { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - - if ( pthread_mutex_init(&mMutex, &attr) < 0 ) - Error("Unable to create pthread mutex: %s", strerror(errno)); -} - -Condition::Condition( Mutex &mutex ) : mMutex( mutex ) { - if ( pthread_cond_init( &mCondition, nullptr ) < 0 ) - throw ThreadException( stringtf( "Unable to create pthread condition: %s", strerror(errno) ) ); -} - -Condition::~Condition() { - if ( pthread_cond_destroy( &mCondition ) < 0 ) - Error("Unable to destroy pthread condition: %s", strerror(errno)); -} - -void Condition::wait() { - // Locking done outside of this function - if ( pthread_cond_wait(&mCondition, mMutex.getMutex()) < 0 ) - throw ThreadException(stringtf("Unable to wait pthread condition: %s", strerror(errno))); -} - -bool Condition::wait(int secs) { - // Locking done outside of this function - Debug(8, "Waiting for %d seconds", secs); - struct timespec timeout = getTimeout(secs); - if ( - ( pthread_cond_timedwait(&mCondition, mMutex.getMutex(), &timeout) < 0 ) - && - ( errno != ETIMEDOUT ) - ) - throw ThreadException(stringtf("Unable to timedwait pthread condition: %s", strerror(errno))); - return errno != ETIMEDOUT; -} - -bool Condition::wait( double secs ) { - // Locking done outside of this function - struct timespec timeout = getTimeout( secs ); - if ( - (pthread_cond_timedwait( &mCondition, mMutex.getMutex(), &timeout ) < 0) - && - (errno != ETIMEDOUT) ) - throw ThreadException( stringtf( "Unable to timedwait pthread condition: %s", strerror(errno) ) ); - return errno != ETIMEDOUT; -} - -void Condition::signal() { - if ( pthread_cond_signal( &mCondition ) < 0 ) - throw ThreadException( stringtf( "Unable to signal pthread condition: %s", strerror(errno) ) ); -} - -void Condition::broadcast() { - if ( pthread_cond_broadcast( &mCondition ) < 0 ) - throw ThreadException( stringtf( "Unable to broadcast pthread condition: %s", strerror(errno) ) ); -} - -template const T ThreadData::getValue() const { - mMutex.lock(); - const T valueCopy = mValue; - mMutex.unlock(); - return valueCopy; -} - -template T ThreadData::setValue(const T value) { - mMutex.lock(); - const T valueCopy = mValue = value; - mMutex.unlock(); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue() const { - Debug(8, "Waiting for value update, %p", this); - mMutex.lock(); - mChanged = false; - mCondition.wait(); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue(double secs) const { - Debug(8, "Waiting for value update, %.2f secs, %p", secs, this); - mMutex.lock(); - mChanged = false; - //do { - mCondition.wait(secs); - //} while ( !mChanged ); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this); - return valueCopy; -} - -template const T ThreadData::getUpdatedValue(int secs) const { - Debug(8, "Waiting for value update, %d secs, %p", secs, this); - mMutex.lock(); - mChanged = false; - //do { - mCondition.wait(secs); - //} while ( !mChanged ); - const T valueCopy = mValue; - mMutex.unlock(); - Debug(9, "Got value update, %p", this); - return valueCopy; -} - -template void ThreadData::updateValueSignal(const T value) { - Debug(8, "Updating value with signal, %p", this); - mMutex.lock(); - mValue = value; - mChanged = true; - mCondition.signal(); - mMutex.unlock(); - Debug(9, "Updated value, %p", this); -} - -template void ThreadData::updateValueBroadcast( const T value ) { - Debug(8, "Updating value with broadcast, %p", this); - mMutex.lock(); - mValue = value; - mChanged = true; - mCondition.broadcast(); - mMutex.unlock(); - Debug(9, "Updated value, %p", this); -} - -Thread::Thread() : - mThreadCondition( mThreadMutex ), - mPid( -1 ), - mStarted( false ), - mRunning( false ) -{ - Debug( 1, "Creating thread" ); -} - -Thread::~Thread() { - Debug( 1, "Destroying thread %d", mPid ); - if ( mStarted ) - join(); -} - -void *Thread::mThreadFunc( void *arg ) { - Debug( 2, "Invoking thread" ); - - Thread *thisPtr = (Thread *)arg; - thisPtr->status = 0; - try { - thisPtr->mThreadMutex.lock(); - thisPtr->mPid = thisPtr->id(); - thisPtr->mThreadCondition.signal(); - thisPtr->mThreadMutex.unlock(); - thisPtr->mRunning = true; - thisPtr->status = thisPtr->run(); - thisPtr->mRunning = false; - Debug( 2, "Exiting thread, status %p", (void *)&(thisPtr->status) ); - return (void *)&(thisPtr->status); - } catch ( const ThreadException &e ) { - Error( "%s", e.getMessage().c_str() ); - thisPtr->mRunning = false; - Debug( 2, "Exiting thread after exception, status %p", (void *)-1 ); - return (void *)-1; - } -} - -void Thread::start() { - Debug( 1, "Starting thread" ); - if ( isThread() ) - throw ThreadException( "Can't self start thread" ); - mThreadMutex.lock(); - if ( !mStarted ) { - pthread_attr_t threadAttrs; - pthread_attr_init( &threadAttrs ); - pthread_attr_setscope( &threadAttrs, PTHREAD_SCOPE_SYSTEM ); - - mStarted = true; - if ( pthread_create( &mThread, &threadAttrs, mThreadFunc, this ) < 0 ) - throw ThreadException( stringtf( "Can't create thread: %s", strerror(errno) ) ); - pthread_attr_destroy( &threadAttrs ); - } else { - Error( "Attempt to start already running thread %d", mPid ); - } - mThreadCondition.wait(); - mThreadMutex.unlock(); - Debug( 1, "Started thread %d", mPid ); -} - -void Thread::join() { - Debug( 1, "Joining thread %d", mPid ); - if ( isThread() ) - throw ThreadException( "Can't self join thread" ); - mThreadMutex.lock(); - if ( mPid >= 0 ) { - if ( mStarted ) { - void *threadStatus = 0; - if ( pthread_join( mThread, &threadStatus ) < 0 ) - throw ThreadException( stringtf( "Can't join sender thread: %s", strerror(errno) ) ); - mStarted = false; - Debug( 1, "Thread %d exited, status %p", mPid, threadStatus ); - } else { - Warning( "Attempt to join already finished thread %d", mPid ); - } - } else { - Warning( "Attempt to join non-started thread %d", mPid ); - } - mThreadMutex.unlock(); - Debug( 1, "Joined thread %d", mPid ); -} - -void Thread::kill( int signal ) { - pthread_kill( mThread, signal ); -} - -// Some explicit template instantiations -#include "zm_threaddata.cpp" diff --git a/src/zm_thread.h b/src/zm_thread.h deleted file mode 100644 index 3f3e8c6b9..000000000 --- a/src/zm_thread.h +++ /dev/null @@ -1,269 +0,0 @@ -// -// ZoneMinder Thread Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZM_THREAD_H -#define ZM_THREAD_H - -class RecursiveMutex; - - -#include "zm_config.h" -#include -#include -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_H -#include "zm_exception.h" -#include "zm_utils.h" -#ifdef __FreeBSD__ -#include -#endif - -class ThreadException : public Exception { -private: -#ifndef SOLARIS - pid_t pid() { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - # else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t pid() { return( pthread_self() ); } -#endif -public: - explicit ThreadException(const std::string &message) : - Exception(stringtf("(%d) ", (long int)pid())+message) - { - } -}; - -class Mutex { - friend class Condition; - - private: - pthread_mutex_t mMutex; - - public: - Mutex(); - ~Mutex(); - - private: - pthread_mutex_t *getMutex() { - return &mMutex; - } - - public: - int trylock(); - void lock(); - void lock( int secs ); - void lock( double secs ); - void unlock(); - bool locked(); -}; - -class RecursiveMutex : public Mutex { - private: - pthread_mutex_t mMutex; - public: - RecursiveMutex(); -}; - -class ScopedMutex { -private: - Mutex &mMutex; - -public: - explicit ScopedMutex( Mutex &mutex ) : mMutex( mutex ) { - mMutex.lock(); - } - ~ScopedMutex() { - mMutex.unlock(); - } - -private: - ScopedMutex( const ScopedMutex & ); -}; - -class Condition { -private: - Mutex &mMutex; - pthread_cond_t mCondition; - -public: - explicit Condition(Mutex &mutex); - ~Condition(); - - void wait(); - bool wait(int secs); - bool wait(double secs); - void signal(); - void broadcast(); -}; - -class Semaphore : public Condition { -private: - Mutex mMutex; - -public: - Semaphore() : Condition(mMutex) { - } - - void wait() { - mMutex.lock(); - Condition::wait(); - mMutex.unlock(); - } - bool wait(int secs) { - mMutex.lock(); - bool result = Condition::wait(secs); - mMutex.unlock(); - return result; - } - bool wait(double secs) { - mMutex.lock(); - bool result = Condition::wait(secs); - mMutex.unlock(); - return result; - } - void signal() { - mMutex.lock(); - Condition::signal(); - mMutex.unlock(); - } - void broadcast() { - mMutex.lock(); - Condition::broadcast(); - mMutex.unlock(); - } -}; - -template class ThreadData { -private: - T mValue; - mutable bool mChanged; - mutable Mutex mMutex; - mutable Condition mCondition; - -public: - __attribute__((used)) ThreadData() : - mValue(0), mCondition(mMutex) - { - mChanged = false; - } - explicit __attribute__((used)) ThreadData(T value) : - mValue(value), mCondition(mMutex) { - mChanged = false; - } - - __attribute__((used)) operator T() const { - return getValue(); - } - __attribute__((used)) const T operator=( const T value ) { - return setValue(value); - } - - __attribute__((used)) const T getValueImmediate() const { - return mValue; - } - __attribute__((used)) T setValueImmediate( const T value ) { - return mValue = value; - } - __attribute__((used)) const T getValue() const; - __attribute__((used)) T setValue( const T value ); - __attribute__((used)) const T getUpdatedValue() const; - __attribute__((used)) const T getUpdatedValue(double secs) const; - __attribute__((used)) const T getUpdatedValue(int secs) const; - __attribute__((used)) void updateValueSignal(const T value); - __attribute__((used)) void updateValueBroadcast(const T value); -}; - -class Thread { -public: - typedef void *(*ThreadFunc)( void * ); - -protected: - pthread_t mThread; - - Mutex mThreadMutex; - Condition mThreadCondition; -#ifndef SOLARIS - pid_t mPid; -#else - pthread_t mPid; -#endif - bool mStarted; - bool mRunning; - int status; // Used in various functions to get around return a local variable - -protected: - Thread(); - virtual ~Thread(); - -#ifndef SOLARIS - pid_t id() const { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - - #else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t id() const { - return pthread_self(); - } -#endif - void exit( int p_status = 0 ) { - //INFO( "Exiting" ); - pthread_exit( (void *)&p_status ); - } - static void *mThreadFunc( void *arg ); - -public: - virtual int run() = 0; - - void start(); - void join(); - void kill( int signal ); - bool isThread() { - return( mPid > -1 && pthread_equal( pthread_self(), mThread ) ); - } - bool isStarted() const { return mStarted; } - bool isRunning() const { return mRunning; } -}; - -#endif // ZM_THREAD_H diff --git a/src/zm_time.h b/src/zm_time.h index 0f7dbc794..5a54bea46 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -20,8 +20,7 @@ #ifndef ZM_TIME_H #define ZM_TIME_H -#include "zm.h" - +#include #include // Structure used for storing the results of the subtraction @@ -76,7 +75,9 @@ struct DeltaTimeval #define USEC_PER_SEC 1000000 #define MSEC_PER_SEC 1000 +/* extern struct timeval tv; +*/ inline int tvDiffUsec( struct timeval first, struct timeval last ) { diff --git a/src/zm_timer.cpp b/src/zm_timer.cpp deleted file mode 100644 index e6d6d16da..000000000 --- a/src/zm_timer.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// -// ZoneMinder Timer Class Implementation, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#include "zm_timer.h" - -#include "zm_logger.h" - -int Timer::TimerThread::mNextTimerId = 0; - -Timer::TimerThread::TimerThread( Timer &timer, int duration, bool repeat ) : - mTimerId( 0 ), - mTimer( timer ), - mDuration( duration ), - mRepeat( repeat ), - mReset( false ), - mExpiryFlag( true ) -{ - mAccessMutex.lock(); - mTimerId = mNextTimerId++; - Debug( 5, "Creating timer %d for %d seconds%s", mTimerId, mDuration, mRepeat?", repeating":"" ); - mAccessMutex.unlock(); -} - -Timer::TimerThread::~TimerThread() -{ - cancel(); -} - -void Timer::TimerThread::cancel() -{ - mAccessMutex.lock(); - if ( mRunning ) - { - Debug( 4, "Cancelling timer %d", mTimerId ); - mRepeat = false; - mReset = false; - mExpiryFlag.updateValueSignal( false ); - } - mAccessMutex.unlock(); -} - -void Timer::TimerThread::reset() -{ - mAccessMutex.lock(); - if ( mRunning ) - { - Debug( 4, "Resetting timer" ); - mReset = true; - mExpiryFlag.updateValueSignal( false ); - } - else - { - Error( "Attempting to reset expired timer %d", mTimerId ); - } - mAccessMutex.unlock(); -} - -int Timer::TimerThread::run() -{ - Debug( 4, "Starting timer %d for %d seconds", mTimerId, mDuration ); - bool timerExpired = false; - do - { - mAccessMutex.lock(); - mReset = false; - mExpiryFlag.setValue( true ); - mAccessMutex.unlock(); - timerExpired = mExpiryFlag.getUpdatedValue( mDuration ); - mAccessMutex.lock(); - if ( timerExpired ) - { - Debug( 4, "Timer %d expired", mTimerId ); - mTimer.expire(); - } - else - { - Debug( 4, "Timer %d %s", mTimerId, mReset?"reset":"cancelled" ); - } - mAccessMutex.unlock(); - } while ( mRepeat || (mReset && !timerExpired) ); - return( timerExpired ); -} - -Timer::Timer( int timeout, bool repeat ) : mTimerThread( *this, timeout, repeat ) -{ - mTimerThread.start(); -} - -Timer::~Timer() -{ - //cancel(); -} - -void Timer::Timer::cancel() -{ - mTimerThread.cancel(); -} - -void Timer::Timer::reset() -{ - mTimerThread.reset(); -} - diff --git a/src/zm_timer.h b/src/zm_timer.h deleted file mode 100644 index da3b95783..000000000 --- a/src/zm_timer.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// ZoneMinder Timer Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -#ifndef ZM_TIMER_H -#define ZM_TIMER_H - -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_H -#include "zm_thread.h" - -#include "zm_exception.h" - -class Timer -{ -private: - class TimerException : public Exception - { - private: -#ifndef SOLARIS - pid_t pid() { - pid_t tid; -#ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; -#else - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - #else - tid=syscall(SYS_gettid); - #endif -#endif - return tid; - } -#else - pthread_t pid() { return( pthread_self() ); } -#endif - public: - explicit TimerException( const std::string &message ) : Exception( stringtf("(%d) ", (long int)pid())+message ) { - } - }; - - class TimerThread : public Thread - { - private: - typedef ThreadData ExpiryFlag; - - private: - static int mNextTimerId; - - private: - int mTimerId; - Timer &mTimer; - int mDuration; - int mRepeat; - int mReset; - ExpiryFlag mExpiryFlag; - Mutex mAccessMutex; - - private: - void quit() - { - cancel(); - } - - public: - TimerThread( Timer &timer, int timeout, bool repeat ); - ~TimerThread(); - - void cancel(); - void reset(); - int run(); - }; - -protected: - TimerThread mTimerThread; - -protected: - Timer( int timeout, bool repeat=false ); - -public: - virtual ~Timer(); - -protected: - virtual void expire()=0; - -public: - void cancel(); - void reset(); -}; - -#endif // ZM_TIMER_H diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 06b53abbe..254663a04 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -17,16 +17,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "zm.h" -#include "zm_db.h" - #include "zm_user.h" -#include -#include -#include -#include -#include +#include "zm_crypt.h" +#include "zm_logger.h" +#include "zm_utils.h" +#include #if HAVE_GNUTLS_GNUTLS_H #include @@ -38,9 +34,6 @@ #include #endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO -#include "zm_utils.h" -#include "zm_crypt.h" - User::User() { id = 0; username[0] = password[0] = 0; @@ -61,7 +54,7 @@ User::User(const MYSQL_ROW &dbrow) { system = (Permission)atoi(dbrow[index++]); char *monitor_ids_str = dbrow[index++]; if ( monitor_ids_str && *monitor_ids_str ) { - StringVector ids = split(monitor_ids_str, ","); + StringVector ids = Split(monitor_ids_str, ","); for ( StringVector::iterator i = ids.begin(); i < ids.end(); ++i ) { monitor_ids.push_back(atoi((*i).c_str())); } @@ -101,43 +94,35 @@ bool User::canAccess(int monitor_id) { // Function to load a user from username and password // Please note that in auth relay mode = none, password is NULL User *zmLoadUser(const char *username, const char *password) { - char sql[ZM_SQL_MED_BUFSIZ] = ""; int username_length = strlen(username); - char *safer_username = new char[(username_length * 2) + 1]; // According to docs, size of safer_whatever must be 2*length+1 // due to unicode conversions + null terminator. - mysql_real_escape_string(&dbconn, safer_username, username, username_length); + std::string escaped_username((username_length * 2) + 1, '\0'); - snprintf(sql, sizeof(sql), - "SELECT `Id`, `Username`, `Password`, `Enabled`," - " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," - " `MonitorIds`" - " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", - safer_username); - delete[] safer_username; - safer_username = nullptr; - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped_username[0], username, username_length); + escaped_username.resize(escaped_len); - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`," + " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," + " `MonitorIds`" + " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", + escaped_username.c_str()); + + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) + return nullptr; if ( mysql_num_rows(result) == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row(result); User *user = new User(dbrow); - mysql_free_result(result); if ( (! password ) // relay type must be none || verifyPassword(username, password, user->getPassword()) ) { + mysql_free_result(result); Info("Authenticated user '%s'", user->getUsername()); return user; } @@ -148,7 +133,7 @@ User *zmLoadUser(const char *username, const char *password) { return nullptr; } // end User *zmLoadUser(const char *username, const char *password) -User *zmLoadTokenUser(std::string jwt_token_str, bool use_remote_addr) { +User *zmLoadTokenUser(const std::string &jwt_token_str, bool use_remote_addr) { std::string key = config.auth_hash_secret; std::string remote_addr = ""; @@ -172,22 +157,13 @@ User *zmLoadTokenUser(std::string jwt_token_str, bool use_remote_addr) { return nullptr; } - char sql[ZM_SQL_MED_BUFSIZ] = ""; - snprintf(sql, sizeof(sql), - "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0," - " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" - " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); + std::string sql = stringtf("SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0," + " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" + " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) return nullptr; - } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - return nullptr; - } int n_users = mysql_num_rows(result); if ( n_users != 1 ) { @@ -233,23 +209,15 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { } } - Debug(1, "Attempting to authenticate user from auth string '%s'", auth); - char sql[ZM_SQL_SML_BUFSIZ] = ""; - snprintf(sql, sizeof(sql), - "SELECT `Id`, `Username`, `Password`, `Enabled`," - " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," - " `MonitorIds` FROM `Users` WHERE `Enabled` = 1"); + Debug(1, "Attempting to authenticate user from auth string '%s', remote addr(%s)", auth, remote_addr); + std::string sql = "SELECT `Id`, `Username`, `Password`, `Enabled`," + " `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0," + " `MonitorIds` FROM `Users` WHERE `Enabled` = 1"; - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) return nullptr; - } + int n_users = mysql_num_rows(result); if ( n_users < 1 ) { mysql_free_result(result); @@ -257,13 +225,14 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { return nullptr; } + // getting the time is expensive, so only do it once. time_t now = time(nullptr); unsigned int hours = config.auth_hash_ttl; - if ( ! hours ) { + if ( !hours ) { Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); hours = 2; } else { - Debug(1, "AUTH_HASH_TTL is %d", hours); + Debug(1, "AUTH_HASH_TTL is %d, time is %" PRIi64, hours, static_cast(now)); } char auth_key[512] = ""; char auth_md5[32+1] = ""; @@ -272,22 +241,23 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { const char * hex = "0123456789abcdef"; while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { - const char *user = dbrow[1]; - const char *pass = dbrow[2]; + const char *username = dbrow[1]; + const char *password = dbrow[2]; time_t our_now = now; + tm now_tm = {}; for ( unsigned int i = 0; i < hours; i++, our_now -= 3600 ) { - struct tm *now_tm = localtime(&our_now); + localtime_r(&our_now, &now_tm); snprintf(auth_key, sizeof(auth_key)-1, "%s%s%s%s%d%d%d%d", config.auth_hash_secret, - user, - pass, + username, + password, remote_addr, - now_tm->tm_hour, - now_tm->tm_mday, - now_tm->tm_mon, - now_tm->tm_year); + now_tm.tm_hour, + now_tm.tm_mday, + now_tm.tm_mon, + now_tm.tm_year); #if HAVE_DECL_MD5 MD5((unsigned char *)auth_key, strlen(auth_key), md5sum); diff --git a/src/zm_user.h b/src/zm_user.h index 42fe07554..a8b78b488 100644 --- a/src/zm_user.h +++ b/src/zm_user.h @@ -17,12 +17,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "zm.h" -#include "zm_db.h" + #ifndef ZM_USER_H #define ZM_USER_H +#include "zm_db.h" #include #include @@ -46,13 +46,13 @@ class User { User(); explicit User(const MYSQL_ROW &dbrow); ~User(); - User(User &u) { Copy(u); } + User(const User &u) { Copy(u); } void Copy(const User &u); User& operator=(const User &u) { Copy(u); return *this; } - const int Id() const { return id; } + int Id() const { return id; } const char *getUsername() const { return username; } const char *getPassword() const { return password; } bool isEnabled() const { return enabled; } @@ -66,7 +66,7 @@ class User { User *zmLoadUser(const char *username, const char *password=0); User *zmLoadAuthUser(const char *auth, bool use_remote_addr); -User *zmLoadTokenUser(std::string jwt, bool use_remote_addr); +User *zmLoadTokenUser(const std::string &jwt, bool use_remote_addr); bool checkUser(const char *username); bool checkPass(const char *password); diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 0f7a9bca9..7d2bbc888 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -17,156 +17,135 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -//#include "zm_logger.h" -#include "zm.h" #include "zm_utils.h" -#include -#include -#include -#include +#include "zm_config.h" +#include "zm_logger.h" +#include +#include #include /* Definition of AT_* constants */ +#include #include + #if defined(__arm__) #include #endif -#ifdef HAVE_CURL_CURL_H -#include -#endif - unsigned int sse_version = 0; unsigned int neonversion = 0; -std::string trimSet(std::string str, std::string trimset) { - // Trim Both leading and trailing sets - size_t startpos = str.find_first_not_of(trimset); // Find the first character position after excluding leading blank spaces - size_t endpos = str.find_last_not_of(trimset); // Find the first character position from reverse af - +// Trim Both leading and trailing sets +std::string Trim(const std::string &str, const std::string &char_set) { + size_t start_pos = str.find_first_not_of(char_set); + size_t end_pos = str.find_last_not_of(char_set); + // if all spaces or empty return an empty string - if ( ( std::string::npos == startpos ) || ( std::string::npos == endpos ) ) - return std::string(""); - return str.substr(startpos, endpos-startpos+1); + if ((start_pos == std::string::npos) || (end_pos == std::string::npos)) + return ""; + return str.substr(start_pos, end_pos - start_pos + 1); } -std::string trimSpaces(const std::string &str) { - return trimSet(str, " \t"); -} - -std::string replaceAll(std::string str, std::string from, std::string to) { - if ( from.empty() ) +std::string ReplaceAll(std::string str, const std::string &old_value, const std::string &new_value) { + if (old_value.empty()) return str; size_t start_pos = 0; - while ( (start_pos = str.find(from, start_pos)) != std::string::npos ) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + while ((start_pos = str.find(old_value, start_pos)) != std::string::npos) { + str.replace(start_pos, old_value.length(), new_value); + start_pos += new_value.length(); // In case 'new_value' contains 'old_value', like replacing 'x' with 'yx' } return str; } -const std::string stringtf( const char *format, ... ) { - va_list ap; - char tempBuffer[8192]; - std::string tempString; +StringVector Split(const std::string &str, char delim) { + std::vector tokens; - va_start(ap, format); - vsnprintf(tempBuffer, sizeof(tempBuffer), format , ap); - va_end(ap); + size_t start = 0; + for (size_t end = str.find(delim); end != std::string::npos; end = str.find(delim, start)) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; + } - tempString = tempBuffer; + tokens.push_back(str.substr(start)); - return tempString; + return tokens; } -const std::string stringtf(const std::string format, ...) { - va_list ap; - char tempBuffer[8192]; - std::string tempString; +StringVector Split(const std::string &str, const std::string &delim, size_t limit) { + StringVector tokens; + size_t start = 0; - va_start(ap, format); - vsnprintf(tempBuffer, sizeof(tempBuffer), format.c_str(), ap); - va_end(ap); - - tempString = tempBuffer; - - return tempString; -} - -bool startsWith(const std::string &haystack, const std::string &needle) { - return ( haystack.substr(0, needle.length()) == needle ); -} - -StringVector split(const std::string &string, const std::string &chars, int limit) { - StringVector stringVector; - std::string tempString = string; - std::string::size_type startIndex = 0; - std::string::size_type endIndex = 0; - - //Info( "Looking for '%s' in '%s', limit %d", chars.c_str(), string.c_str(), limit ); do { - // Find delimiters - endIndex = string.find_first_of( chars, startIndex ); - //Info( "Got endIndex at %d", endIndex ); - if ( endIndex > 0 ) { - //Info( "Adding '%s'", string.substr( startIndex, endIndex-startIndex ).c_str() ); - stringVector.push_back( string.substr( startIndex, endIndex-startIndex ) ); + size_t end = str.find_first_of(delim, start); + if (end > 0) { + tokens.push_back(str.substr(start, end - start)); } - if ( endIndex == std::string::npos ) + if (end == std::string::npos) { break; + } // Find non-delimiters - startIndex = tempString.find_first_not_of( chars, endIndex ); - if ( limit && (stringVector.size() == (unsigned int)(limit-1)) ) { - stringVector.push_back( string.substr( startIndex ) ); + start = str.find_first_not_of(delim, end); + if (limit && (tokens.size() == limit - 1)) { + tokens.push_back(str.substr(start)); break; } - //Info( "Got new startIndex at %d", startIndex ); - } while ( startIndex != std::string::npos ); - //Info( "Finished with %d strings", stringVector.size() ); + } while (start != std::string::npos); - return stringVector; + return tokens; } -const std::string join(const StringVector &v, const char * delim=",") { +std::pair PairSplit(const std::string &str, char delim) { + if (str.empty()) + return std::make_pair("", ""); + + size_t pos = str.find(delim); + + if (pos == std::string::npos) + return std::make_pair("", ""); + + return std::make_pair(str.substr(0, pos), str.substr(pos + 1, std::string::npos)); +} + +std::string Join(const StringVector &values, const std::string &delim) { std::stringstream ss; - for (size_t i = 0; i < v.size(); ++i) { - if ( i != 0 ) + for (size_t i = 0; i < values.size(); ++i) { + if (i != 0) ss << delim; - ss << v[i]; + ss << values[i]; } return ss.str(); } -const std::string base64Encode(const std::string &inString) { - static char base64_table[64] = { '\0' }; +std::string Base64Encode(const std::string &str) { + static char base64_table[64] = {'\0'}; - if ( !base64_table[0] ) { + if (!base64_table[0]) { int i = 0; - for ( char c = 'A'; c <= 'Z'; c++ ) + for (char c = 'A'; c <= 'Z'; c++) base64_table[i++] = c; - for ( char c = 'a'; c <= 'z'; c++ ) + for (char c = 'a'; c <= 'z'; c++) base64_table[i++] = c; - for ( char c = '0'; c <= '9'; c++ ) + for (char c = '0'; c <= '9'; c++) base64_table[i++] = c; base64_table[i++] = '+'; base64_table[i++] = '/'; } std::string outString; - outString.reserve(2 * inString.size()); + outString.reserve(2 * str.size()); - const char *inPtr = inString.c_str(); - while ( *inPtr ) { + const char *inPtr = str.c_str(); + while (*inPtr) { unsigned char selection = *inPtr >> 2; unsigned char remainder = (*inPtr++ & 0x03) << 4; outString += base64_table[selection]; - if ( *inPtr ) { + if (*inPtr) { selection = remainder | (*inPtr >> 4); remainder = (*inPtr++ & 0x0f) << 2; outString += base64_table[selection]; - - if ( *inPtr ) { + + if (*inPtr) { selection = remainder | (*inPtr >> 6); outString += base64_table[selection]; selection = (*inPtr++ & 0x3f); @@ -184,114 +163,64 @@ const std::string base64Encode(const std::string &inString) { return outString; } -int split(const char* string, const char delim, std::vector& items) { - if ( string == nullptr ) - return -1; - - if ( string[0] == 0 ) - return -2; - - std::string str(string); - - while ( true ) { - size_t pos = str.find(delim); - items.push_back(str.substr(0, pos)); - str.erase(0, pos+1); - - if ( pos == std::string::npos ) - break; +void TimespecDiff(struct timespec *start, struct timespec *end, struct timespec *diff) { + if (((end->tv_nsec) - (start->tv_nsec)) < 0) { + diff->tv_sec = end->tv_sec - start->tv_sec - 1; + diff->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec; + } else { + diff->tv_sec = end->tv_sec - start->tv_sec; + diff->tv_nsec = end->tv_nsec - start->tv_nsec; } - - return items.size(); } -int pairsplit(const char* string, const char delim, std::string& name, std::string& value) { - if ( string == nullptr ) - return -1; +std::string TimevalToString(timeval tv) { + tm now = {}; + std::array tm_buf = {}; - if ( string[0] == 0 ) - return -2; + localtime_r(&tv.tv_sec, &now); + size_t tm_buf_len = strftime(tm_buf.data(), tm_buf.size(), "%Y-%m-%d %H:%M:%S", &now); + if (tm_buf_len == 0) { + return ""; + } - std::string str(string); - size_t pos = str.find(delim); - - if ( pos == std::string::npos || pos == 0 || pos >= str.length() ) - return -3; - - name = str.substr(0, pos); - value = str.substr(pos+1, std::string::npos); - - return 0; + return stringtf("%s.%06ld", tm_buf.data(), tv.tv_usec); } /* Detect special hardware features, such as SIMD instruction sets */ -void hwcaps_detect() { +void HwCapsDetect() { neonversion = 0; sse_version = 0; #if (defined(__i386__) || defined(__x86_64__)) - /* x86 or x86-64 processor */ - uint32_t r_edx, r_ecx, r_ebx; + __builtin_cpu_init(); -#ifdef __x86_64__ - __asm__ __volatile__( - "push %%rbx\n\t" - "mov $0x0,%%ecx\n\t" - "mov $0x7,%%eax\n\t" - "cpuid\n\t" - "push %%rbx\n\t" - "mov $0x1,%%eax\n\t" - "cpuid\n\t" - "pop %%rax\n\t" - "pop %%rbx\n\t" - : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) - : - : - ); -#else - __asm__ __volatile__( - "push %%ebx\n\t" - "mov $0x0,%%ecx\n\t" - "mov $0x7,%%eax\n\t" - "cpuid\n\t" - "push %%ebx\n\t" - "mov $0x1,%%eax\n\t" - "cpuid\n\t" - "pop %%eax\n\t" - "pop %%ebx\n\t" - : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) - : - : - ); -#endif - - if ( r_ebx & 0x00000020 ) { + if (__builtin_cpu_supports("avx2")) { sse_version = 52; /* AVX2 */ Debug(1, "Detected a x86\\x86-64 processor with AVX2"); - } else if ( r_ecx & 0x10000000 ) { + } else if (__builtin_cpu_supports("avx")) { sse_version = 51; /* AVX */ Debug(1, "Detected a x86\\x86-64 processor with AVX"); - } else if ( r_ecx & 0x00100000 ) { + } else if (__builtin_cpu_supports("sse4.2")) { sse_version = 42; /* SSE4.2 */ Debug(1, "Detected a x86\\x86-64 processor with SSE4.2"); - } else if ( r_ecx & 0x00080000 ) { + } else if (__builtin_cpu_supports("sse4.1")) { sse_version = 41; /* SSE4.1 */ Debug(1, "Detected a x86\\x86-64 processor with SSE4.1"); - } else if ( r_ecx & 0x00000200 ) { + } else if (__builtin_cpu_supports("ssse3")) { sse_version = 35; /* SSSE3 */ - Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); - } else if ( r_ecx & 0x00000001 ) { + Debug(1, "Detected a x86\\x86-64 processor with SSSE3"); + } else if (__builtin_cpu_supports("sse3")) { sse_version = 30; /* SSE3 */ Debug(1, "Detected a x86\\x86-64 processor with SSE3"); - } else if ( r_edx & 0x04000000 ) { + } else if (__builtin_cpu_supports("sse2")) { sse_version = 20; /* SSE2 */ Debug(1, "Detected a x86\\x86-64 processor with SSE2"); - } else if ( r_edx & 0x02000000 ) { + } else if (__builtin_cpu_supports("sse")) { sse_version = 10; /* SSE */ Debug(1, "Detected a x86\\x86-64 processor with SSE"); } else { sse_version = 0; Debug(1, "Detected a x86\\x86-64 processor"); - } + } #elif defined(__arm__) // ARM processor in 32bit mode // To see if it supports NEON, we need to get that information from the kernel @@ -316,19 +245,19 @@ void hwcaps_detect() { /* SSE2 aligned memory copy. Useful for big copying of aligned memory like image buffers in ZM */ /* For platforms without SSE2 we will use standard x86 asm memcpy or glibc's memcpy() */ #if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("sse2"))) +__attribute__((noinline, __target__("sse2"))) #endif -void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes) { +void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - if ( bytes > 128 ) { + if (bytes > 128) { unsigned int remainder = bytes % 128; - const uint8_t* lastsrc = (uint8_t*)src + (bytes - remainder); + const uint8_t *lastsrc = (uint8_t *) src + (bytes - remainder); __asm__ __volatile__( "sse2_copy_iter:\n\t" "movdqa (%0),%%xmm0\n\t" "movdqa 0x10(%0),%%xmm1\n\t" - "movdqa 0x20(%0),%%xmm2\n\t" + "movdqa 0x20(%0),%%xmm2\n\t" "movdqa 0x30(%0),%%xmm3\n\t" "movdqa 0x40(%0),%%xmm4\n\t" "movdqa 0x50(%0),%%xmm5\n\t" @@ -358,90 +287,120 @@ void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes) { } else { /* Standard memcpy */ - __asm__ __volatile__("cld; rep movsb" :: "S"(src), "D"(dest), "c"(bytes) : "cc", "memory"); + __asm__ __volatile__("cld; rep movsb"::"S"(src), "D"(dest), "c"(bytes) : "cc", "memory"); } #else /* Non x86\x86-64 platform, use memcpy */ - memcpy(dest, src, bytes); + memcpy(dest,src,bytes); #endif return dest; } -void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff) { - if (((end->tv_nsec)-(start->tv_nsec))<0) { - diff->tv_sec = end->tv_sec-start->tv_sec-1; - diff->tv_nsec = 1000000000+end->tv_nsec-start->tv_nsec; - } else { - diff->tv_sec = end->tv_sec-start->tv_sec; - diff->tv_nsec = end->tv_nsec-start->tv_nsec; - } -} - -char *timeval_to_string( struct timeval tv ) { - time_t nowtime; - struct tm *nowtm; - static char tmbuf[20], buf[28]; - - nowtime = tv.tv_sec; - nowtm = localtime(&nowtime); - strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); - snprintf(buf, sizeof buf-1, "%s.%06ld", tmbuf, tv.tv_usec); - return buf; -} - -std::string UriDecode( const std::string &encoded ) { - char a, b; - const char *src = encoded.c_str(); - std::string retbuf; - retbuf.resize(encoded.length() + 1); - char *dst = &retbuf[0]; - while (*src) { - if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { - if (a >= 'a') - a -= 'a'-'A'; - if (a >= 'A') - a -= ('A' - 10); - else - a -= '0'; - if (b >= 'a') - b -= 'a'-'A'; - if (b >= 'A') - b -= ('A' - 10); - else - b -= '0'; - *dst++ = 16*a+b; - src+=3; - } else if (*src == '+') { - *dst++ = ' '; - src++; - } else { - *dst++ = *src++; - } - } - *dst++ = '\0'; - return retbuf; -} - -void string_toupper( std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), ::toupper); -} - void touch(const char *pathname) { - int fd = open(pathname, - O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, - 0666); - if ( fd < 0 ) { + int fd = open(pathname, O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666); + if (fd < 0) { // Couldn't open that path. Error("Couldn't open() path %s in touch", pathname); return; } - int rc = utimensat(AT_FDCWD, - pathname, - nullptr, - 0); - if ( rc ) { + int rc = utimensat(AT_FDCWD, pathname, nullptr, 0); + if (rc) { Error("Couldn't utimensat() path %s in touch", pathname); return; } } +std::string UriDecode(const std::string &encoded) { + const char *src = encoded.c_str(); + std::string retbuf; + retbuf.reserve(encoded.length()); + while (*src) { + char a, b; + if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { + if (a >= 'a') + a -= 'a' - 'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a' - 'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + retbuf.push_back(16 * a + b); + src += 3; + } else if (*src == '+') { + retbuf.push_back(' '); + src++; + } else { + retbuf.push_back(*src++); + } + } + return retbuf; +} + +QueryString::QueryString(std::istream &input) { + while (!input.eof() && input.peek() > 0) { + //Should eat "param1=" + auto name = parseName(input); + //Should eat value1& + std::string value = parseValue(input); + + auto foundItr = parameters_.find(name); + if (foundItr == parameters_.end()) { + std::unique_ptr newParam = ZM::make_unique(name); + if (!value.empty()) { + newParam->addValue(value); + } + parameters_.emplace(name, std::move(newParam)); + } else { + foundItr->second->addValue(value); + } + } +} + +std::vector QueryString::names() const { + std::vector names; + for (auto const &pair : parameters_) + names.push_back(pair.second->name()); + + return names; +} + +const QueryParameter *QueryString::get(const std::string &name) const { + auto itr = parameters_.find(name); + return itr == parameters_.end() ? nullptr : itr->second.get(); +} + +std::string QueryString::parseName(std::istream &input) { + std::string name; + + while (!input.eof() && input.peek() != '=') { + name.push_back(input.get()); + } + + //Eat the '=' + if (!input.eof()) { + input.get(); + } + + return name; +} + +std::string QueryString::parseValue(std::istream &input) { + std::string url_encoded_value; + + int c = input.get(); + while (c > 0 && c != '&') { + url_encoded_value.push_back(c); + c = input.get(); + } + + if (url_encoded_value.empty()) { + return ""; + } + + return UriDecode(url_encoded_value); +} diff --git a/src/zm_utils.h b/src/zm_utils.h index 9c8cc01a0..88e00a785 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -20,49 +20,130 @@ #ifndef ZM_UTILS_H #define ZM_UTILS_H -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include typedef std::vector StringVector; -std::string trimSpaces(const std::string &str); -std::string trimSet(std::string str, std::string trimset); -std::string replaceAll(std::string str, std::string from, std::string to); +std::string Trim(const std::string &str, const std::string &char_set); +inline std::string TrimSpaces(const std::string &str) { return Trim(str, " \t"); } +std::string ReplaceAll(std::string str, const std::string& old_value, const std::string& new_value); +inline void StringToUpper(std::string &str) { std::transform(str.begin(), str.end(), str.begin(), ::toupper); } -const std::string stringtf( const char *format, ... ); -const std::string stringtf( const std::string &format, ... ); +StringVector Split(const std::string &str, char delim); +StringVector Split(const std::string &str, const std::string &delim, size_t limit = 0); +std::pair PairSplit(const std::string &str, char delim); -bool startsWith( const std::string &haystack, const std::string &needle ); -StringVector split( const std::string &string, const std::string &chars, int limit=0 ); -const std::string join( const StringVector &, const char * ); +std::string Join(const StringVector &values, const std::string &delim = ","); -const std::string base64Encode( const std::string &inString ); -void string_toupper(std::string& str); - -int split(const char* string, const char delim, std::vector& items); -int pairsplit(const char* string, const char delim, std::string& name, std::string& value); - -inline int max( int a, int b ) -{ - return( a>=b?a:b ); +inline bool StartsWith(const std::string &haystack, const std::string &needle) { + return (haystack.substr(0, needle.length()) == needle); } -inline int min( int a, int b ) -{ - return( a<=b?a:b ); +template +std::string stringtf(const std::string &format, Args... args) { + int size = snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + if (size <= 0) { + throw std::runtime_error("Error during formatting."); + } + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside } -void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); -void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); +std::string Base64Encode(const std::string &str); + +void TimespecDiff(timespec *start, timespec *end, timespec *diff); +std::string TimevalToString(timeval tv); -void hwcaps_detect(); extern unsigned int sse_version; extern unsigned int neonversion; +void HwCapsDetect(); +void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes); -char *timeval_to_string( struct timeval tv ); -std::string UriDecode( const std::string &encoded ); -void touch( const char *pathname ); +void touch(const char *pathname); + +namespace ZM { +//! std::make_unique implementation (TODO: remove this once C++14 is supported) +template +inline auto make_unique(Args &&...args) -> +typename std::enable_if::value, std::unique_ptr>::type { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +inline auto make_unique(std::size_t size) -> +typename std::enable_if::value && std::extent::value == 0, std::unique_ptr>::type { + return std::unique_ptr(new typename std::remove_extent::type[size]()); +} + +template +inline auto make_unique(Args &&...) -> +typename std::enable_if::value != 0, void>::type = delete; + +template +constexpr const T &clamp(const T &v, const T &lo, const T &hi, Compare comp) { + return comp(v, lo) ? lo : comp(hi, v) ? hi : v; +} + +template +constexpr const T &clamp(const T &v, const T &lo, const T &hi) { + return clamp(v, lo, hi, std::less{}); +} +} + +typedef std::chrono::microseconds Microseconds; +typedef std::chrono::milliseconds Milliseconds; +typedef std::chrono::seconds Seconds; +typedef std::chrono::minutes Minutes; +typedef std::chrono::hours Hours; + +typedef std::chrono::steady_clock::time_point TimePoint; +typedef std::chrono::system_clock::time_point SystemTimePoint; + +std::string UriDecode(const std::string &encoded); + +class QueryParameter { + public: + explicit QueryParameter(std::string name) : name_(std::move(name)) {} + + const std::string &name() const { return name_; } + const std::string &firstValue() const { return values_[0]; } + + const std::vector &values() const { return values_; } + size_t size() const { return values_.size(); } + + template + void addValue(T &&value) { values_.emplace_back(std::forward(value)); } + private: + std::string name_; + std::vector values_; +}; + +class QueryString { + public: + explicit QueryString(std::istream &input); + + size_t size() const { return parameters_.size(); } + bool has(const char *name) const { return parameters_.find(std::string(name)) != parameters_.end(); } + + std::vector names() const; + + const QueryParameter *get(const std::string &name) const; + const QueryParameter *get(const char *name) const { return get(std::string(name)); }; + + private: + static std::string parseName(std::istream &input); + static std::string parseValue(std::istream &input); + + std::map> parameters_; +}; #endif // ZM_UTILS_H diff --git a/src/zm_video.cpp b/src/zm_video.cpp deleted file mode 100644 index d97c75167..000000000 --- a/src/zm_video.cpp +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright (C) 2001-2017 ZoneMinder LLC -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// -#include "zm.h" -#include "zm_video.h" -#include "zm_image.h" -#include "zm_utils.h" -#include "zm_rgb.h" -#include -#include -#include - -VideoWriter::VideoWriter( - const char* p_container, - const char* p_codec, - const char* p_path, - const unsigned int p_width, - const unsigned int p_height, - const unsigned int p_colours, - const unsigned int p_subpixelorder) : - container(p_container), - codec(p_codec), - path(p_path), - width(p_width), - height(p_height), - colours(p_colours), - subpixelorder(p_subpixelorder), - frame_count(0) { - Debug(7, "Video object created"); - - /* Parameter checking */ - if ( path.empty() ) { - Error("Invalid file path"); - } - if ( !width || !height ) { - Error("Invalid width or height"); - } -} - -VideoWriter::~VideoWriter() { - Debug(7, "Video object destroyed"); -} - -int VideoWriter::Reset(const char* new_path) { - /* Common variables reset */ - - /* If there is a new path, use it */ - if ( new_path != nullptr ) { - path = new_path; - } - - /* Reset frame counter */ - frame_count = 0; - - return 0; -} - - -#if ZM_HAVE_VIDEOWRITER_X264MP4 -X264MP4Writer::X264MP4Writer( - const char* p_path, - const unsigned int p_width, - const unsigned int p_height, - const unsigned int p_colours, - const unsigned int p_subpixelorder, - const std::vector* p_user_params) : - VideoWriter( - "mp4", - "h264", - p_path, - p_width, - p_height, - p_colours, - p_subpixelorder), - bOpen(false), - bGotH264AVCInfo(false), - bFirstFrame(true) { - /* Initialize ffmpeg if it hasn't been initialized yet */ - FFMPEGInit(); - - /* Initialize swscale */ - zm_pf = GetFFMPEGPixelFormat(colours, subpixelorder); - if ( zm_pf == 0 ) { - Error("Unable to match ffmpeg pixelformat"); - } - codec_pf = AV_PIX_FMT_YUV420P; - - if ( ! swscaleobj.init() ) { - Error("Failed init swscaleobj"); - return; - } - - swscaleobj.SetDefaults(zm_pf, codec_pf, width, height); - - /* Calculate the image sizes. We will need this for parameter checking */ - zm_imgsize = colours * width * height; -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - codec_imgsize = av_image_get_buffer_size(codec_pf, width, height, 1); -#else - codec_imgsize = avpicture_get_size(codec_pf, width, height); -#endif - if ( !codec_imgsize ) { - Error("Failed calculating codec pixel format image size"); - } - - /* If supplied with user parameters to the encoder, copy them */ - if ( p_user_params != nullptr ) { - user_params = *p_user_params; - } - - /* Setup x264 parameters */ - if ( x264config() < 0 ) { - Error("Failed setting x264 parameters"); - } - - /* Allocate x264 input picture */ - x264_picture_alloc( - &x264picin, - X264_CSP_I420, - x264params.i_width, - x264params.i_height); -} - -X264MP4Writer::~X264MP4Writer() { - /* Free x264 input picture */ - x264_picture_clean(&x264picin); - - if ( bOpen ) - Close(); -} - -int X264MP4Writer::Open() { - /* Open the encoder */ - x264enc = x264_encoder_open(&x264params); - if ( x264enc == nullptr ) { - Error("Failed opening x264 encoder"); - return -1; - } - - // Debug(4,"x264 maximum delayed frames: %d", - // x264_encoder_maximum_delayed_frames(x264enc)); - - x264_nal_t* nals; - int i_nals; - if ( !x264_encoder_headers(x264enc, &nals, &i_nals) ) { - Error("Failed getting encoder headers"); - return -2; - } - - /* Search SPS NAL for AVC information */ - for ( int i = 0; i < i_nals; i++ ) { - if ( nals[i].i_type == NAL_SPS ) { - x264_profleindication = nals[i].p_payload[5]; - x264_profilecompat = nals[i].p_payload[6]; - x264_levelindication = nals[i].p_payload[7]; - bGotH264AVCInfo = true; - break; - } - } - if ( !bGotH264AVCInfo ) { - Warning("Missing AVC information"); - } - - /* Create the file */ - mp4h = MP4Create((path + ".incomplete").c_str()); - if ( mp4h == MP4_INVALID_FILE_HANDLE ) { - Error("Failed creating mp4 file: %s", path.c_str()); - return -10; - } - - /* Set the global timescale */ - if ( !MP4SetTimeScale(mp4h, 1000) ) { - Error("Failed setting timescale"); - return -11; - } - - /* Set the global video profile */ - /* I am a bit confused about this one. - I couldn't find what the value should be - Some use 0x15 while others use 0x7f. */ - MP4SetVideoProfileLevel(mp4h, 0x7f); - - /* Add H264 video track */ - mp4vtid = MP4AddH264VideoTrack( - mp4h, - 1000, - MP4_INVALID_DURATION, - width, - height, - x264_profleindication, - x264_profilecompat, - x264_levelindication, - 3); - if ( mp4vtid == MP4_INVALID_TRACK_ID ) { - Error("Failed adding H264 video track"); - return -12; - } - - bOpen = true; - - return 0; -} - -int X264MP4Writer::Close() { - /* Flush all pending frames */ - for ( int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) { -Debug(1,"Encoding delayed frame"); - if ( x264encodeloop(true) < 0 ) - break; - } - - /* Close the encoder */ - x264_encoder_close(x264enc); - - /* Close MP4 handle */ - MP4Close(mp4h); - - Debug(1,"Optimising"); - /* Required for proper HTTP streaming */ - MP4Optimize((path + ".incomplete").c_str(), path.c_str()); - - /* Delete the temporary file */ - unlink((path + ".incomplete").c_str()); - - bOpen = false; - - Debug(1, "Video closed. Total frames: %d", frame_count); - - return 0; -} - -int X264MP4Writer::Reset(const char* new_path) { - /* Close the encoder and file */ - if ( bOpen ) - Close(); - - /* Reset common variables */ - VideoWriter::Reset(new_path); - - /* Reset local variables */ - bFirstFrame = true; - bGotH264AVCInfo = false; - prevnals.clear(); - prevpayload.clear(); - - /* Reset x264 parameters */ - x264config(); - - /* Open the encoder */ - Open(); - - return 0; -} - -int X264MP4Writer::Encode( - const uint8_t* data, - const size_t data_size, - const unsigned int frame_time) { - /* Parameter checking */ - if ( data == nullptr ) { - Error("NULL buffer"); - return -1; - } - - if ( data_size != zm_imgsize ) { - Error("The data buffer size (%d) != expected (%d)", data_size, zm_imgsize); - return -2; - } - - if ( !bOpen ) { - Warning("The encoder was not initialized, initializing now"); - Open(); - } - - /* Convert the image into the x264 input picture */ - if ( swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0 ) { - Error("Image conversion failed"); - return -3; - } - - /* Set PTS */ - x264picin.i_pts = frame_time; - - /* Do the encoding */ - x264encodeloop(); - - /* Increment frame counter */ - frame_count++; - - return 0; -} - -int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) { - if ( img->Width() != width ) { - Error("Source image width differs. Source: %d Output: %d", img->Width(), width); - return -12; - } - - if ( img->Height() != height ) { - Error("Source image height differs. Source: %d Output: %d", img->Height(), height); - return -13; - } - - return Encode(img->Buffer(), img->Size(), frame_time); -} - -int X264MP4Writer::x264config() { - /* Sets up the encoder configuration */ - - int x264ret; - - /* Defaults */ - const char* preset = "veryfast"; - const char* tune = "stillimage"; - const char* profile = "main"; - - /* Search the user parameters for preset, tune and profile */ - for ( unsigned int i = 0; i < user_params.size(); i++ ) { - if ( strcmp(user_params[i].pname, "preset") == 0 ) { - /* Got preset */ - preset = user_params[i].pvalue; - } else if ( strcmp(user_params[i].pname, "tune") == 0 ) { - /* Got tune */ - tune = user_params[i].pvalue; - } else if ( strcmp(user_params[i].pname, "profile") == 0 ) { - /* Got profile */ - profile = user_params[i].pvalue; - } - } - - /* Set the defaults and preset and tune */ - x264ret = x264_param_default_preset(&x264params, preset, tune); - if ( x264ret != 0 ) { - Error("Failed setting x264 preset %s and tune %s : %d", preset, tune, x264ret); - } - - /* Set the profile */ - x264ret = x264_param_apply_profile(&x264params, profile); - if ( x264ret != 0 ) { - Error("Failed setting x264 profile %s : %d", profile, x264ret); - } - - /* Input format */ - x264params.i_width = width; - x264params.i_height = height; - x264params.i_csp = X264_CSP_I420; - - /* Quality control */ - x264params.rc.i_rc_method = X264_RC_CRF; - x264params.rc.f_rf_constant = 23.0; - - /* Enable b-frames */ - x264params.i_bframe = 16; - x264params.i_bframe_adaptive = 1; - - /* Timebase */ - x264params.i_timebase_num = 1; - x264params.i_timebase_den = 1000; - - /* Enable variable frame rate */ - x264params.b_vfr_input = 1; - - /* Disable annex-b (start codes) */ - x264params.b_annexb = 0; - - /* TODO: Setup error handler */ - if ( logDebugging() ) - x264params.i_log_level = X264_LOG_DEBUG; - else - x264params.i_log_level = X264_LOG_NONE; - - /* Process user parameters (excluding preset, tune and profile) */ - for ( unsigned int i = 0; i < user_params.size(); i++ ) { - /* Skip preset, tune and profile */ - if ( - (strcmp(user_params[i].pname, "preset") == 0) || - (strcmp(user_params[i].pname, "tune") == 0) || - (strcmp(user_params[i].pname, "profile") == 0) ) { - continue; - } - - /* Pass the name and value to x264 */ - x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue); - - /* Error checking */ - if ( x264ret != 0 ) { - if ( x264ret == X264_PARAM_BAD_NAME ) { - Error("Failed processing x264 user parameter %s=%s : Bad name", - user_params[i].pname, user_params[i].pvalue); - } else if ( x264ret == X264_PARAM_BAD_VALUE ) { - Error("Failed processing x264 user parameter %s=%s : Bad value", - user_params[i].pname, user_params[i].pvalue); - } else { - Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", - user_params[i].pname, user_params[i].pvalue, x264ret); - } - } - } - - return 0; -} - -int X264MP4Writer::x264encodeloop(bool bFlush) { - x264_nal_t* nals; - int i_nals; - int frame_size; - - if ( bFlush ) { - frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, nullptr, &x264picout); - } else { - frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout); - } - - if ( frame_size > 0 || bFlush ) { - Debug(1, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n", - frame_count, x264picout.i_pts, x264picout.i_dts, frame_size); - - /* Handle the previous frame */ - if ( !bFirstFrame ) { - buffer.clear(); - - /* Process the NALs for the previous frame */ - for ( unsigned int i = 0; i < prevnals.size(); i++ ) { - Debug(9, "Processing NAL: Type %d Size %d", - prevnals[i].i_type, - prevnals[i].i_payload); - - switch ( prevnals[i].i_type ) { - case NAL_PPS: - /* PPS NAL */ - MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); - break; - case NAL_SPS: - /* SPS NAL */ - MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); - break; - default: - /* Anything else, hopefully frames, so copy it into the sample */ - buffer.append(prevnals[i].p_payload, prevnals[i].i_payload); - } - } - - /* Calculate frame duration and offset */ - int duration = x264picout.i_dts - prevDTS; - int offset = prevPTS - prevDTS; - - /* Write the sample */ - if ( !buffer.empty() ) { - unsigned int bufSize = buffer.size(); - if ( !MP4WriteSample( - mp4h, - mp4vtid, - buffer.extract(bufSize), - bufSize, - duration, - offset, - prevKeyframe) ) { - Error("Failed writing sample"); - } - } - - /* Cleanup */ - prevnals.clear(); - prevpayload.clear(); - } - - /* Got a frame. Copy this new frame into the previous frame */ - if ( frame_size > 0 ) { - /* Copy the NALs and the payloads */ - for ( int i = 0; i < i_nals; i++ ) { - prevnals.push_back(nals[i]); - prevpayload.append(nals[i].p_payload, nals[i].i_payload); - } - - /* Update the payload pointers */ - /* This is done in a separate loop because the previous loop might reallocate memory when appending, - making the pointers invalid */ - unsigned int payload_offset = 0; - for ( unsigned int i = 0; i < prevnals.size(); i++ ) { - prevnals[i].p_payload = prevpayload.head() + payload_offset; - payload_offset += nals[i].i_payload; - } - - /* We need this for the next frame */ - prevPTS = x264picout.i_pts; - prevDTS = x264picout.i_dts; - prevKeyframe = x264picout.b_keyframe; - - bFirstFrame = false; - } - - } else if ( frame_size == 0 ) { - Debug(1, "x264 encode returned zero. Delayed frames: %d", - x264_encoder_delayed_frames(x264enc)); - } else { - Error("x264 encode failed: %d", frame_size); - } - return frame_size; -} -#endif // ZM_VIDEOWRITER_X264MP4 - -int ParseEncoderParameters( - const char* str, - std::vector* vec - ) { - if ( vec == nullptr ) { - Error("NULL Encoder parameters vector pointer"); - return -1; - } - - if ( str == nullptr ) { - Error("NULL Encoder parameters string"); - return -2; - } - - vec->clear(); - - if ( str[0] == 0 ) { - /* Empty */ - return 0; - } - - std::string line; - std::stringstream ss(str); - size_t valueoffset; - size_t valuelen; - unsigned int lineno = 0; - EncoderParameter_t param; - - while ( std::getline(ss, line) ) { - lineno++; - - /* Remove CR if exists */ - if ( line.length() >= 1 && line[line.length()-1] == '\r' ) { - line.erase(line.length() - 1); - } - - /* Skip comments and empty lines */ - if ( line.empty() || line[0] == '#' ) { - continue; - } - - valueoffset = line.find('='); - if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) { - Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); - continue; - } - - if ( valueoffset > (sizeof(param.pname) - 1 ) ) { - Warning("Failed parsing encoder parameters line %d: Name too long", lineno); - continue; - } - - valuelen = line.length() - (valueoffset+1); - - if ( valuelen > (sizeof(param.pvalue) - 1 ) ) { - Warning("Failed parsing encoder parameters line %d: Value too long", lineno); - continue; - } - - /* Copy and NULL terminate */ - line.copy(param.pname, valueoffset, 0); - line.copy(param.pvalue, valuelen, valueoffset+1); - param.pname[valueoffset] = 0; - param.pvalue[valuelen] = 0; - - /* Push to the vector */ - vec->push_back(param); - - Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue); - } - - Debug(7, "Parsed %d lines", lineno); - - return 0; -} - - diff --git a/src/zm_video.h b/src/zm_video.h deleted file mode 100644 index e185f82fc..000000000 --- a/src/zm_video.h +++ /dev/null @@ -1,174 +0,0 @@ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef ZM_VIDEO_H -#define ZM_VIDEO_H - -#include "zm.h" -#include "zm_rgb.h" -#include "zm_utils.h" -#include "zm_ffmpeg.h" -#include "zm_buffer.h" -#include "zm_swscale.h" - -/* -#define HAVE_LIBX264 1 -#define HAVE_LIBMP4V2 1 -#define HAVE_X264_H 1 -#define HAVE_MP4_H 1 -*/ - -#if HAVE_MP4V2_MP4V2_H -#include -#endif -#if HAVE_MP4V2_H -#include -#endif -#if HAVE_MP4_H -#include -#endif - -#if HAVE_X264_H -#ifdef __cplusplus -extern "C" { -#endif -#include -#ifdef __cplusplus -} -#endif -#endif - -/* Structure for user parameters to the encoder */ -struct EncoderParameter_t { - char pname[48]; - char pvalue[48]; - -}; -int ParseEncoderParameters(const char* str, std::vector* vec); - -/* VideoWriter is a generic interface that ZM uses to save events as videos */ -/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */ - -class VideoWriter { - -protected: - std::string container; - std::string codec; - std::string path; - unsigned int width; - unsigned int height; - unsigned int colours; - unsigned int subpixelorder; - - unsigned int frame_count; - -public: - VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); - virtual ~VideoWriter(); - virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0; - virtual int Encode(const Image* img, const unsigned int frame_time) = 0; - virtual int Open() = 0; - virtual int Close() = 0; - virtual int Reset(const char* new_path = nullptr); - - const char* GetContainer() const { - return container.c_str(); - } - const char* GetCodec() const { - return codec.c_str(); - } - const char* GetPath() const { - return path.c_str(); - } - unsigned int GetWidth() const { - return width; - } - unsigned int GetHeight() const { - return height; - } - unsigned int GetColours() const { - return colours; - } - unsigned int GetSubpixelorder () const { - return subpixelorder; - } - unsigned int GetFrameCount() const { - return frame_count; - } -}; - -#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE -#define ZM_HAVE_VIDEOWRITER_X264MP4 1 -class X264MP4Writer : public VideoWriter { - -protected: - - bool bOpen; - bool bGotH264AVCInfo; - bool bFirstFrame; - - /* SWScale */ - SWScale swscaleobj; - enum _AVPIXELFORMAT zm_pf; - enum _AVPIXELFORMAT codec_pf; - size_t codec_imgsize; - size_t zm_imgsize; - - /* User parameters */ - std::vector user_params; - - /* AVC Information */ - uint8_t x264_profleindication; - uint8_t x264_profilecompat; - uint8_t x264_levelindication; - - /* NALs */ - Buffer buffer; - - /* Previous frame */ - int prevPTS; - int prevDTS; - bool prevKeyframe; - Buffer prevpayload; - std::vector prevnals; - - /* Internal functions */ - int x264config(); - int x264encodeloop(bool bFlush = false); - - /* x264 objects */ - x264_t* x264enc; - x264_param_t x264params; - x264_picture_t x264picin; - x264_picture_t x264picout; - - /* MP4v2 objects */ - MP4FileHandle mp4h; - MP4TrackId mp4vtid; - - -public: - X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params = nullptr); - ~X264MP4Writer(); - int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time); - int Encode(const Image* img, const unsigned int frame_time); - int Open(); - int Close(); - int Reset(const char* new_path = nullptr); - -}; -#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE - -#endif // ZM_VIDEO_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 8892c6443..843558de3 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -18,49 +18,101 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#define __STDC_FORMAT_MACROS 1 - - -#include "zm.h" #include "zm_videostore.h" -#include -#include -#include +#include "zm_logger.h" +#include "zm_monitor.h" extern "C" { #include "libavutil/time.h" } +/* + AVCodecID codec_id; + char *codec_codec; + char *codec_name; + enum AVPixelFormat sw_pix_fmt; + enum AVPixelFormat hw_pix_fmt; + AVHWDeviceType hwdevice_type; + */ + +VideoStore::CodecData VideoStore::codec_data[] = { +#if HAVE_LIBAVUTIL_HWCONTEXT_H + { AV_CODEC_ID_H265, "h265", "hevc_vaapi", AV_PIX_FMT_NV12, AV_PIX_FMT_VAAPI, AV_HWDEVICE_TYPE_VAAPI }, + { AV_CODEC_ID_H265, "h265", "hevc_nvenc", AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, AV_HWDEVICE_TYPE_NONE }, + { AV_CODEC_ID_H265, "h265", "libx265", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_NONE }, + + { AV_CODEC_ID_H264, "h264", "h264_vaapi", AV_PIX_FMT_NV12, AV_PIX_FMT_VAAPI, AV_HWDEVICE_TYPE_VAAPI }, + { AV_CODEC_ID_H264, "h264", "h264_nvenc", AV_PIX_FMT_NV12, AV_PIX_FMT_NV12, AV_HWDEVICE_TYPE_NONE }, + { AV_CODEC_ID_H264, "h264", "h264_omx", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_NONE }, + { AV_CODEC_ID_H264, "h264", "h264", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_NONE }, + { AV_CODEC_ID_H264, "h264", "libx264", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P, AV_HWDEVICE_TYPE_NONE }, + { AV_CODEC_ID_MJPEG, "mjpeg", "mjpeg", AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ422P, AV_HWDEVICE_TYPE_NONE }, +#else + { AV_CODEC_ID_H265, "h265", "libx265", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P }, + + { AV_CODEC_ID_H264, "h264", "h264", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P }, + { AV_CODEC_ID_H264, "h264", "libx264", AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P }, + { AV_CODEC_ID_MJPEG, "mjpeg", "mjpeg", AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ422P }, +#endif +}; + VideoStore::VideoStore( const char *filename_in, const char *format_in, AVStream *p_video_in_stream, + AVCodecContext *p_video_in_ctx, AVStream *p_audio_in_stream, + AVCodecContext *p_audio_in_ctx, Monitor *p_monitor - ) { - - video_in_stream = p_video_in_stream; - audio_in_stream = p_audio_in_stream; - monitor = p_monitor; - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //video_in_ctx = avcodec_alloc_context3(NULL); - //avcodec_parameters_to_context(video_in_ctx, - //video_in_stream->codecpar); - //video_in_ctx->time_base = video_in_stream->time_base; -// zm_dump_codecpar( video_in_stream->codecpar ); -#else + ) : + chosen_codec_data(nullptr), + monitor(p_monitor), + out_format(nullptr), + oc(nullptr), + video_out_stream(nullptr), + audio_out_stream(nullptr), + video_out_codec(nullptr), + video_in_ctx(p_video_in_ctx), + video_out_ctx(nullptr), + video_in_stream(p_video_in_stream), + audio_in_stream(p_audio_in_stream), + audio_in_codec(nullptr), + audio_in_ctx(p_audio_in_ctx), + audio_out_codec(nullptr), + audio_out_ctx(nullptr), + video_in_frame(nullptr), + in_frame(nullptr), + out_frame(nullptr), + hw_frame(nullptr), + packets_written(0), + frame_count(0), + hw_device_ctx(nullptr), +#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) + resample_ctx(nullptr), +#if defined(HAVE_LIBSWRESAMPLE) + fifo(nullptr), #endif +#endif + converted_in_samples(nullptr), + filename(filename_in), + format(format_in), + video_first_pts(0), /* starting pts of first in frame/packet */ + video_first_dts(0), + audio_first_pts(0), + audio_first_dts(0), + video_last_pts(AV_NOPTS_VALUE), + audio_last_pts(AV_NOPTS_VALUE), + next_dts(nullptr), + audio_next_pts(0), + max_stream_index(-1) +{ + FFMPEGInit(); + swscale.init(); +} // VideoStore::VideoStore - // In future, we should just pass in the codec context instead of the stream. Don't really need the stream. - video_in_ctx = video_in_stream->codec; - - // store ins in variables local to class - filename = filename_in; - format = format_in; - - Info("Opening video storage stream %s format: %s", filename, format); +bool VideoStore::open() { + Debug(1, "Opening video storage stream %s format: %s", filename, format); int ret = avformat_alloc_output_context2(&oc, nullptr, nullptr, filename); if ( ret < 0 ) { @@ -68,8 +120,6 @@ VideoStore::VideoStore( "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", filename, av_make_error_string(ret).c_str()); - } else { - Debug(4, "Success allocating out format ctx"); } // Couldn't deduce format from filename, trying from format name @@ -80,220 +130,244 @@ VideoStore::VideoStore( "Could not create video storage stream %s as no out ctx" " could not be assigned based on filename or format %s", filename, format); - return; - } else { - Debug(4, "Success allocating out ctx"); + return false; } - } // end if ! oc + } // end if ! oc AVDictionary *pmetadata = nullptr; - int dsr = - av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); oc->metadata = pmetadata; out_format = oc->oformat; out_format->flags |= AVFMT_TS_NONSTRICT; // allow non increasing dts - video_out_codec = avcodec_find_encoder(video_in_ctx->codec_id); - if ( !video_out_codec ) { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Fatal("Could not find encoder for '%s'", avcodec_get_name(video_out_ctx->codec_id)); -#else - Fatal("Could not find encoder for '%d'", video_out_ctx->codec_id); + if ( video_in_stream ) { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + zm_dump_codecpar(video_in_stream->codecpar); #endif - } + if ( monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH ) { + // Don't care what codec, just copy parameters + video_out_ctx = avcodec_alloc_context3(nullptr); + // There might not be a useful video_in_stream. v4l in might not populate this very +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); +#else + ret = avcodec_copy_context(video_out_ctx, video_in_ctx); +#endif + if ( ret < 0 ) { + Error("Could not initialize ctx parameters"); + return false; + } + video_out_ctx->pix_fmt = fix_deprecated_pix_fmt(video_out_ctx->pix_fmt); + if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else + video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif + } + video_out_ctx->time_base = video_in_ctx->time_base; + if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) { + Debug(2,"No timebase found in video in context, defaulting to Q"); + video_out_ctx->time_base = AV_TIME_BASE_Q; + } + } else if ( monitor->GetOptVideoWriter() == Monitor::ENCODE ) { + int wanted_codec = monitor->OutputCodec(); + if ( !wanted_codec ) { + // default to h264 + //Debug(2, "Defaulting to H264"); + //wanted_codec = AV_CODEC_ID_H264; + // FIXME what is the optimal codec? Probably low latency h264 which is effectively mjpeg + } else { + if ( AV_CODEC_ID_H264 != 27 and wanted_codec > 3 ) { + // Older ffmpeg had AV_CODEC_ID_MPEG2VIDEO_XVMC at position 3 has been deprecated + wanted_codec += 1; + } + Debug(2, "Codec wanted %d %s", wanted_codec, avcodec_get_name((AVCodecID)wanted_codec)); + } + std::string wanted_encoder = monitor->Encoder(); - video_out_stream = avformat_new_stream(oc, nullptr); + for (unsigned int i = 0; i < sizeof(codec_data) / sizeof(*codec_data); i++) { + chosen_codec_data = &codec_data[i]; + if (wanted_encoder != "" and wanted_encoder != "auto") { + if (wanted_encoder != codec_data[i].codec_name) { + Debug(1, "Not the right codec name %s != %s", codec_data[i].codec_name, wanted_encoder.c_str()); + continue; + } + } + if (wanted_codec and (codec_data[i].codec_id != wanted_codec)) { + Debug(1, "Not the right codec %d %s != %d %s", + codec_data[i].codec_id, + avcodec_get_name(codec_data[i].codec_id), + wanted_codec, + avcodec_get_name((AVCodecID)wanted_codec) + ); + continue; + } + + video_out_codec = avcodec_find_encoder_by_name(codec_data[i].codec_name); + if (!video_out_codec) { + Debug(1, "Didn't find encoder for %s", codec_data[i].codec_name); + continue; + } + Debug(1, "Found video codec for %s", codec_data[i].codec_name); + video_out_ctx = avcodec_alloc_context3(video_out_codec); + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else + video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif + } + + // When encoding, we are going to use the timestamp values instead of packet pts/dts + video_out_ctx->time_base = AV_TIME_BASE_Q; + video_out_ctx->codec_id = codec_data[i].codec_id; + video_out_ctx->pix_fmt = codec_data[i].hw_pix_fmt; + Debug(1, "Setting pix fmt to %d %s", codec_data[i].hw_pix_fmt, av_get_pix_fmt_name(codec_data[i].hw_pix_fmt)); + video_out_ctx->level = 32; + + // Don't have an input stream, so need to tell it what we are sending it, or are transcoding + video_out_ctx->width = monitor->Width(); + video_out_ctx->height = monitor->Height(); + video_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO; + + if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { + video_out_ctx->bit_rate = 2000000; + video_out_ctx->gop_size = 12; + video_out_ctx->max_b_frames = 1; + } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO ) { + /* just for testing, we also add B frames */ + video_out_ctx->max_b_frames = 2; + } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO ) { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + video_out_ctx->mb_decision = 2; + } +#if HAVE_LIBAVUTIL_HWCONTEXT_H + if (codec_data[i].hwdevice_type != AV_HWDEVICE_TYPE_NONE) { + Debug(1, "Setting up hwdevice"); + ret = av_hwdevice_ctx_create(&hw_device_ctx, + codec_data[i].hwdevice_type, + nullptr, nullptr, 0); + + AVBufferRef *hw_frames_ref; + AVHWFramesContext *frames_ctx = nullptr; + + if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + Error("Failed to create hwaccel frame context."); + return -1; + } + frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); + frames_ctx->format = codec_data[i].hw_pix_fmt; + frames_ctx->sw_format = codec_data[i].sw_pix_fmt; + frames_ctx->width = monitor->Width(); + frames_ctx->height = monitor->Height(); + frames_ctx->initial_pool_size = 20; + if ((ret = av_hwframe_ctx_init(hw_frames_ref)) < 0) { + Error("Failed to initialize hwaccel frame context." + "Error code: %s",av_err2str(ret)); + av_buffer_unref(&hw_frames_ref); + } else { + video_out_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); + if (!video_out_ctx->hw_frames_ctx) { + Error("Failed to allocate hw_frames_ctx"); + } + } + av_buffer_unref(&hw_frames_ref); + } +#endif + + AVDictionary *opts = 0; + std::string Options = monitor->GetEncoderOptions(); + Debug(2, "Options? %s", Options.c_str()); + ret = av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); + if (ret < 0) { + Warning("Could not parse ffmpeg encoder options list '%s'\n", Options.c_str()); + } else { + AVDictionaryEntry *e = nullptr; + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { + Debug(3, "Encoder Option %s=%s", e->key, e->value); + } + } + + if ((ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0) { + if (wanted_encoder != "" and wanted_encoder != "auto") { + Warning("Can't open video codec (%s) %s", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + } else { + Debug(1, "Can't open video codec (%s) %s", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + } + video_out_codec = nullptr; + } + + Debug(1, "Success"); + AVDictionaryEntry *e = nullptr; + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { + Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); + } + if (video_out_codec) break; + avcodec_free_context(&video_out_ctx); + if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); + } // end foreach codec + + if (!video_out_codec) { + Error("Can't open video codec!"); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // We allocate and copy in newer ffmpeg, so need to free it + avcodec_free_context(&video_out_ctx); +#endif + return false; + } // end if can't open codec + Debug(2, "Success opening codec"); + } // end if copying or transcoding + zm_dump_codec(video_out_ctx); + } // end if video_in_stream + + video_out_stream = avformat_new_stream(oc, video_out_codec); if ( !video_out_stream ) { Error("Unable to create video out stream"); - return; - } else { - Debug(2, "Success creating video out stream"); + return false; } max_stream_index = video_out_stream->index; - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // by allocating our own copy, we don't run into the problems when we free the streams - video_out_ctx = avcodec_alloc_context3(video_out_codec); - // Since we are not re-encoding, all we have to do is copy the parameters - // Copy params from instream to ctx - ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); - if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } -#else - video_out_ctx = video_out_stream->codec; - // This will wipe out the codec defaults - ret = avcodec_copy_context(video_out_ctx, video_in_ctx); - if ( ret < 0 ) { - Fatal("Unable to copy in video ctx to out video ctx %s", - av_make_error_string(ret).c_str()); - } else { - Debug(3, "Success copying ctx"); - } -#endif - - // Just copy them from the in, no reason to choose different - video_out_ctx->time_base = video_in_ctx->time_base; - if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) { - Debug(2,"No timebase found in video in context, defaulting to Q"); - video_out_ctx->time_base = AV_TIME_BASE_Q; - } - - zm_dump_codec(video_out_ctx); - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //// Fix deprecated formats - switch ( video_out_ctx->pix_fmt ) { - case AV_PIX_FMT_YUVJ422P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV422P; - break; - case AV_PIX_FMT_YUVJ444P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV444P; - break; - case AV_PIX_FMT_YUVJ440P : - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV440P; - break; - case AV_PIX_FMT_NONE : - case AV_PIX_FMT_YUVJ420P : - default: - video_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P; - break; - } - - if ( !video_out_ctx->codec_tag ) { - Debug(2, "No codec_tag"); - if ( - !oc->oformat->codec_tag - || - av_codec_get_id(oc->oformat->codec_tag, video_in_ctx->codec_tag) == video_out_ctx->codec_id - || - av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id) <= 0 - ) { - Warning("Setting codec tag"); - video_out_ctx->codec_tag = video_in_ctx->codec_tag; - } - } -#endif - - video_out_stream->time_base = video_in_stream->time_base; - if ( video_in_stream->avg_frame_rate.num ) { - Debug(3,"Copying avg_frame_rate (%d/%d)", - video_in_stream->avg_frame_rate.num, - video_in_stream->avg_frame_rate.den - ); - video_out_stream->avg_frame_rate = video_in_stream->avg_frame_rate; - } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( video_in_stream->r_frame_rate.num ) { - Debug(3,"Copying r_frame_rate (%d/%d) to out (%d/%d)", - video_in_stream->r_frame_rate.num, - video_in_stream->r_frame_rate.den , - video_out_stream->r_frame_rate.num, - video_out_stream->r_frame_rate.den - ); - video_out_stream->r_frame_rate = video_in_stream->r_frame_rate; - } -#endif - Debug(3, - "Time bases: VIDEO in stream (%d/%d) in codec: (%d/%d) out " - "stream: (%d/%d) out codec (%d/%d)", - video_in_stream->time_base.num, video_in_stream->time_base.den, - video_in_ctx->time_base.num, video_in_ctx->time_base.den, - video_out_stream->time_base.num, video_out_stream->time_base.den, - video_out_ctx->time_base.num, video_out_ctx->time_base.den); - - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) - video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif - } - -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) -#if 0 -# This is commented out because we are only doing passthrough right now - /* I'm not entirely sure that this is a good idea. We may have to do it someday but really only when transcoding - * * think what I was trying to achieve here was to have zm_dump_codecpar output nice info - * */ - - AVDictionary *opts = 0; - ret = av_dict_parse_string(&opts, monitor->GetOptEncoderParams().c_str(), "=", ",", 0); - if ( ret < 0 ) { - Warning("Could not parse ffmpeg encoder options '%s'", monitor->GetOptEncoderParams().c_str()); - } - - if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { - Warning("Can't open video codec (%s) %s", - video_out_codec->name, - av_make_error_string(ret).c_str() - ); - video_out_codec = nullptr; - } - - AVDictionaryEntry *e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { - Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); - } ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } else { - zm_dump_codec(video_out_ctx); + Error("Could not initialize stream parameteres"); + return false; } #else - ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_in_ctx); - if ( ret < 0 ) { - Error("Could not initialize video_out_ctx parameters"); - return; - } else { - zm_dump_codec(video_out_ctx); - } + avcodec_copy_context(video_out_stream->codec, video_out_ctx); #endif - zm_dump_codecpar(video_in_stream->codecpar); - zm_dump_codecpar(video_out_stream->codecpar); -#endif - + // Only set orientation if doing passthrough, otherwise the frame image will be rotated Monitor::Orientation orientation = monitor->getOrientation(); if ( orientation ) { + Debug(3, "Have orientation %d", orientation); if ( orientation == Monitor::ROTATE_0 ) { } else if ( orientation == Monitor::ROTATE_90 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + ret = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); + if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); } else if ( orientation == Monitor::ROTATE_180 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + ret = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); + if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); } else if ( orientation == Monitor::ROTATE_270 ) { - dsr = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); - if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + ret = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); + if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); } else { Warning("Unsupported Orientation(%d)", orientation); } - } + } // end if orientation + video_out_stream->time_base = video_in_stream ? video_in_stream->time_base : AV_TIME_BASE_Q; - converted_in_samples = nullptr; - audio_out_codec = nullptr; - audio_in_codec = nullptr; - audio_in_ctx = nullptr; - audio_out_stream = nullptr; - in_frame = nullptr; - out_frame = nullptr; -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) - resample_ctx = nullptr; - fifo = nullptr; -#endif - video_first_pts = 0; - video_first_dts = 0; - - audio_first_pts = 0; - audio_first_dts = 0; - - if ( audio_in_stream ) { - Debug(3, "Have audio stream"); + if ( audio_in_stream and audio_in_ctx ) { + Debug(2, "Have audio_in_stream %p", audio_in_stream); if ( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -301,38 +375,47 @@ VideoStore::VideoStore( #else audio_in_stream->codec->codec_id #endif - != AV_CODEC_ID_AAC ) { + != AV_CODEC_ID_AAC + ) { audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); if ( !audio_out_codec ) { Error("Could not find codec for AAC"); - return; - } + } else { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + audio_in_ctx = avcodec_alloc_context3(audio_out_codec); + ret = avcodec_parameters_to_context(audio_in_ctx, + audio_in_stream->codecpar); + audio_in_ctx->time_base = audio_in_stream->time_base; +#else + audio_in_ctx = audio_in_stream->codec; +#endif #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_out_stream = avformat_new_stream(oc, nullptr); - audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { - Error("could not allocate codec ctx for AAC"); - audio_out_stream = nullptr; - return; - } + audio_out_ctx = avcodec_alloc_context3(audio_out_codec); + if ( !audio_out_ctx ) { + Error("could not allocate codec ctx for AAC"); + return false; + } #else - audio_out_stream = avformat_new_stream(oc, audio_out_codec); - audio_out_ctx = audio_out_stream->codec; + audio_out_ctx = audio_out_stream->codec; #endif - audio_out_stream->time_base = audio_in_stream->time_base; + audio_out_stream = avformat_new_stream(oc, audio_out_codec); + audio_out_stream->time_base = audio_in_stream->time_base; - if ( !setup_resampler() ) { - return; - } + if ( !setup_resampler() ) { + return false; + } + } // end if found AAC codec } else { Debug(2, "Got AAC"); - audio_out_stream = avformat_new_stream(oc, nullptr); + // normally we want to pass params from codec in here + // but since we are doing audio passthrough we don't care + audio_out_stream = avformat_new_stream(oc, audio_out_codec); if ( !audio_out_stream ) { Error("Could not allocate new stream"); - return; + return false; } audio_out_stream->time_base = audio_in_stream->time_base; @@ -341,11 +424,11 @@ VideoStore::VideoStore( audio_out_ctx = avcodec_alloc_context3(audio_out_codec); if ( !audio_out_ctx ) { Error("Could not allocate new output_context"); - return; + return false; } // We don't actually care what the time_base is.. - audio_out_ctx->time_base = audio_in_stream->time_base; + audio_out_ctx->time_base = audio_in_ctx->time_base; // Copy params from instream to ctx ret = avcodec_parameters_to_context( @@ -367,8 +450,8 @@ VideoStore::VideoStore( Error("Unable to copy audio ctx %s", av_make_error_string(ret).c_str()); audio_out_stream = nullptr; - return; - } // end if + return false; + } // end if audio_out_ctx->codec_tag = 0; #endif @@ -397,16 +480,13 @@ VideoStore::VideoStore( for ( int i = 0; i <= max_stream_index; i++ ) { next_dts[i] = 0; } -} // VideoStore::VideoStore -bool VideoStore::open() { - int ret; /* open the out file, if needed */ if ( !(out_format->flags & AVFMT_NOFILE) ) { ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr); if ( ret < 0 ) { Error("Could not open out file '%s': %s", filename, - av_make_error_string(ret).c_str()); + av_make_error_string(ret).c_str()); return false; } } @@ -416,7 +496,7 @@ bool VideoStore::open() { AVDictionary *opts = nullptr; - std::string option_string = monitor->GetOptEncoderParams(); + std::string option_string = monitor->GetEncoderOptions(); ret = av_dict_parse_string(&opts, option_string.c_str(), "=", ",\n", 0); if ( ret < 0 ) { Warning("Could not parse ffmpeg output options '%s'", option_string.c_str()); @@ -425,11 +505,8 @@ bool VideoStore::open() { const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE); if ( !movflags_entry ) { Debug(1, "setting movflags to frag_keyframe+empty_moov"); - // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); // Shiboleth reports that this may break seeking in mp4 before it downloads av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); - // av_dict_set(&opts, "movflags", - // "frag_keyframe+empty_moov+default_base_moof", 0); } else { Debug(1, "using movflags %s", movflags_entry->value); } @@ -449,91 +526,116 @@ bool VideoStore::open() { if ( opts ) av_dict_free(&opts); if ( ret < 0 ) { Error("Error occurred when writing out file header to %s: %s", - filename, av_make_error_string(ret).c_str()); - /* free the stream */ + filename, av_make_error_string(ret).c_str()); avio_closep(&oc->pb); - //avformat_free_context(oc); return false; } + + zm_dump_stream_format(oc, 0, 0, 1); + if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); return true; -} // end VideoStore::open() +} // end bool VideoStore::open() -VideoStore::~VideoStore() { +void VideoStore::flush_codecs() { + int ret; + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. + AVPacket pkt; + // Without these we seg fault becuse av_init_packet doesn't init them + pkt.data = nullptr; + pkt.size = 0; + av_init_packet(&pkt); - if ( oc->pb ) { + // I got crashes if the codec didn't do DELAY, so let's test for it. + if ( video_out_ctx->codec && ( video_out_ctx->codec->capabilities & +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AV_CODEC_CAP_DELAY +#else + CODEC_CAP_DELAY +#endif + ) ) { + // Put encoder into flushing mode + while ( (ret = zm_send_frame_receive_packet(video_out_ctx, nullptr, pkt) ) > 0 ) { + av_packet_rescale_ts(&pkt, + video_out_ctx->time_base, + video_out_stream->time_base); + write_packet(&pkt, video_out_stream); + zm_av_packet_unref(&pkt); + } // while have buffered frames + Debug(1, "Done writing buffered video."); + } // end if have delay capability - if ( audio_out_codec ) { + if ( audio_out_codec ) { + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. - // The codec queues data. We need to send a flush command and out - // whatever we get. Failures are not fatal. - AVPacket pkt; - // Without these we seg fault I don't know why. - pkt.data = nullptr; - pkt.size = 0; - av_init_packet(&pkt); - - int frame_size = audio_out_ctx->frame_size; - /* - * At the end of the file, we pass the remaining samples to - * the encoder. */ - while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { - zm_resample_audio(resample_ctx, nullptr, out_frame); - - if ( zm_add_samples_to_fifo(fifo, out_frame) ) { - // Should probably set the frame size to what is reported FIXME - if ( zm_get_samples_from_fifo(fifo, out_frame) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { - pkt.stream_index = audio_out_stream->index; - - av_packet_rescale_ts(&pkt, - audio_out_ctx->time_base, - audio_out_stream->time_base); - write_packet(&pkt, audio_out_stream); - } - } // end if data returned from fifo - } - - } // end if have buffered samples in the resampler - - Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); - while ( av_audio_fifo_size(fifo) > 0 ) { - /* Take one frame worth of audio samples from the FIFO buffer, - * encode it and write it to the output file. */ - - Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", - frame_size, av_audio_fifo_size(fifo)); - - // SHould probably set the frame size to what is reported FIXME - if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { - pkt.stream_index = audio_out_stream->index; + int frame_size = audio_out_ctx->frame_size; + /* + * At the end of the file, we pass the remaining samples to + * the encoder. */ + while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { + zm_resample_audio(resample_ctx, nullptr, out_frame); + if ( zm_add_samples_to_fifo(fifo, out_frame) ) { + // Should probably set the frame size to what is reported FIXME + if ( zm_get_samples_from_fifo(fifo, out_frame) ) { + if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) > 0 ) { av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); } } // end if data returned from fifo - } // end while still data in the fifo + } + + } // end while have buffered samples in the resampler + + Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); + while ( av_audio_fifo_size(fifo) > 0 ) { + /* Take one frame worth of audio samples from the FIFO buffer, + * encode it and write it to the output file. */ + + Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", + frame_size, av_audio_fifo_size(fifo)); + + // SHould probably set the frame size to what is reported FIXME + if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { + if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { + pkt.stream_index = audio_out_stream->index; + + av_packet_rescale_ts(&pkt, + audio_out_ctx->time_base, + audio_out_stream->time_base); + write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); + } + } // end if data returned from fifo + } // end while still data in the fifo #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Put encoder into flushing mode avcodec_send_frame(audio_out_ctx, nullptr); #endif - while (1) { - if ( ! zm_receive_packet(audio_out_ctx, pkt) ) { - Debug(1, "No more packets"); - break; - } + while (1) { + if ( 0 >= zm_receive_packet(audio_out_ctx, pkt) ) { + Debug(1, "No more packets"); + break; + } - dumpPacket(&pkt, "raw from encoder"); - av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); - dumpPacket(audio_out_stream, &pkt, "writing flushed packet"); - write_packet(&pkt, audio_out_stream); - zm_av_packet_unref(&pkt); - } // while have buffered frames - } // end if audio_out_codec + ZM_DUMP_PACKET(pkt, "raw from encoder"); + av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); + ZM_DUMP_STREAM_PACKET(audio_out_stream, pkt, "writing flushed packet"); + write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); + } // while have buffered frames + } // end if audio_out_codec +} // end flush_codecs + +VideoStore::~VideoStore() { + if ( oc->pb ) { + flush_codecs(); // Flush Queues Debug(1, "Flushing interleaved queues"); @@ -552,59 +654,41 @@ VideoStore::~VideoStore() { /* Close the out file. */ Debug(2, "Closing"); if ( int rc = avio_close(oc->pb) ) { - oc->pb = nullptr; Error("Error closing avio %s", av_err2str(rc)); } } else { Debug(3, "Not closing avio because we are not writing to a file."); } - } // end if ( oc->pb ) + oc->pb = nullptr; + } // end if oc->pb // I wonder if we should be closing the file first. // I also wonder if we really need to be doing all the ctx // allocation/de-allocation constantly, or whether we can just re-use it. // Just do a file open/close/writeheader/etc. // What if we were only doing audio recording? + if ( video_out_stream ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // We allocate and copy in newer ffmpeg, so need to free it - //avcodec_free_context(&video_in_ctx); -#endif video_in_ctx = nullptr; - if ( video_out_codec ) { - avcodec_close(video_out_ctx); - Debug(4, "Success closing video_out_ctx"); - video_out_codec = nullptr; - } // end if video_out_codec -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + Debug(4, "Freeing video_out_ctx"); avcodec_free_context(&video_out_ctx); -#endif - video_out_ctx = nullptr; } // end if video_out_stream if ( audio_out_stream ) { - if ( audio_in_codec ) { - avcodec_close(audio_in_ctx); - Debug(4, "Success closing audio_in_ctx"); - audio_in_codec = nullptr; - } // end if audio_in_codec - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it - avcodec_free_context(&audio_in_ctx); + //avcodec_free_context(&audio_in_ctx); #endif - Debug(4, "Success freeing audio_in_ctx"); - audio_in_ctx = nullptr; + //Debug(4, "Success freeing audio_in_ctx"); + audio_in_codec = nullptr; if ( audio_out_ctx ) { - avcodec_close(audio_out_ctx); Debug(4, "Success closing audio_out_ctx"); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&audio_out_ctx); #endif } - audio_out_ctx = nullptr; #if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) if ( resample_ctx ) { @@ -636,16 +720,16 @@ VideoStore::~VideoStore() { #endif } // end if audio_out_stream + Debug(4, "free context"); /* free the streams */ avformat_free_context(oc); delete[] next_dts; + next_dts = nullptr; } // VideoStore::~VideoStore() bool VideoStore::setup_resampler() { #if !defined(HAVE_LIBSWRESAMPLE) && !defined(HAVE_LIBAVRESAMPLE) - Error( - "Not built with resample library. " - "Cannot do audio conversion to AAC"); + Error("%s", "Not built with resample library. Cannot do audio conversion to AAC"); return false; #else int ret; @@ -668,25 +752,12 @@ bool VideoStore::setup_resampler() { // codec is already open in ffmpeg_camera audio_in_ctx = audio_in_stream->codec; audio_in_codec = reinterpret_cast(audio_in_ctx->codec); - if ( !audio_in_codec ) { + if (!audio_in_codec) { audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); + if (!audio_in_codec) { + return false; + } } - if ( !audio_in_codec ) { - return false; - } -#endif - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -#else -#if 0 - ret = avcodec_copy_context(audio_in_ctx, audio_in_stream->codec); - if ( ret < 0 ) { - Fatal("Unable to copy in video ctx to out video ctx %s", - av_make_error_string(ret).c_str()); - } else { - Debug(3, "Success copying ctx"); - } -#endif #endif // if the codec is already open, nothing is done. @@ -707,12 +778,13 @@ bool VideoStore::setup_resampler() { /* put sample parameters */ audio_out_ctx->bit_rate = audio_in_ctx->bit_rate <= 32768 ? audio_in_ctx->bit_rate : 32768; audio_out_ctx->sample_rate = audio_in_ctx->sample_rate; + audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) if ( !audio_out_ctx->channel_layout ) { - Debug(3, "Correcting channel layout from (%d) to (%d)", + Debug(3, "Correcting channel layout from (%" PRIi64 ") to (%" PRIi64 ")", audio_out_ctx->channel_layout, av_get_default_channel_layout(audio_out_ctx->channels) ); @@ -732,7 +804,7 @@ bool VideoStore::setup_resampler() { Debug(3, "Sample rate is good %d", audio_out_ctx->sample_rate); } else { audio_out_ctx->sample_rate = - audio_out_codec->supported_samplerates[0]; + audio_out_codec->supported_samplerates[0]; Debug(1, "Sample rate is no good, setting to (%d)", audio_out_codec->supported_samplerates[0]); } @@ -740,14 +812,16 @@ bool VideoStore::setup_resampler() { /* check that the encoder supports s16 pcm in */ if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) { - Debug(2, "Encoder does not support sample format %s, setting to FLTP", - av_get_sample_fmt_name(audio_out_ctx->sample_fmt)); + Debug(3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name(audio_out_ctx->sample_fmt)); audio_out_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; } - audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; + // Example code doesn't set the codec tb. I think it just uses whatever defaults + //audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; AVDictionary *opts = nullptr; + // Needed to allow AAC if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { Error("Couldn't set experimental"); } @@ -763,10 +837,11 @@ bool VideoStore::setup_resampler() { } zm_dump_codec(audio_out_ctx); + audio_out_stream->time_base = (AVRational){1, audio_out_ctx->sample_rate}; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context( - audio_out_stream->codecpar, audio_out_ctx); - if ( ret < 0 ) { + if ( (ret = avcodec_parameters_from_context( + audio_out_stream->codecpar, + audio_out_ctx)) < 0 ) { Error("Could not initialize stream parameteres"); return false; } @@ -782,22 +857,37 @@ bool VideoStore::setup_resampler() { audio_out_ctx->time_base.num, audio_out_ctx->time_base.den); Debug(1, - "Audio in bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) frame_size(%d)", + "Audio in bit_rate (%" AV_PACKET_DURATION_FMT ") sample_rate(%d) channels(%d) fmt(%d) layout(%" PRIi64 ") frame_size(%d)", audio_in_ctx->bit_rate, audio_in_ctx->sample_rate, audio_in_ctx->channels, audio_in_ctx->sample_fmt, audio_in_ctx->channel_layout, audio_in_ctx->frame_size); Debug(1, - "Audio out bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) frame_size(%d)", + "Audio out context bit_rate (%" AV_PACKET_DURATION_FMT ") sample_rate(%d) channels(%d) fmt(%d) layout(% " PRIi64 ") frame_size(%d)", audio_out_ctx->bit_rate, audio_out_ctx->sample_rate, audio_out_ctx->channels, audio_out_ctx->sample_fmt, audio_out_ctx->channel_layout, audio_out_ctx->frame_size); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + Debug(1, + "Audio out stream bit_rate (%" PRIi64 ") sample_rate(%d) channels(%d) fmt(%d) layout(%" PRIi64 ") frame_size(%d)", + audio_out_stream->codecpar->bit_rate, audio_out_stream->codecpar->sample_rate, + audio_out_stream->codecpar->channels, audio_out_stream->codecpar->format, + audio_out_stream->codecpar->channel_layout, audio_out_stream->codecpar->frame_size); +#else + Debug(1, + "Audio out bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " + "layout(%" PRIi64 ") frame_size(%d)", + audio_out_stream->codec->bit_rate, audio_out_stream->codec->sample_rate, + audio_out_stream->codec->channels, audio_out_stream->codec->sample_fmt, + audio_out_stream->codec->channel_layout, audio_out_stream->codec->frame_size); +#endif + /** Create a new frame to store the audio samples. */ - if ( !(in_frame = zm_av_frame_alloc()) ) { - Error("Could not allocate in frame"); - return false; + if ( ! in_frame ) { + if (!(in_frame = zm_av_frame_alloc())) { + Error("Could not allocate in frame"); + return false; + } } /** Create a new frame to store the audio samples. */ @@ -806,6 +896,7 @@ bool VideoStore::setup_resampler() { av_frame_free(&in_frame); return false; } + out_frame->sample_rate = audio_out_ctx->sample_rate; if ( !(fifo = av_audio_fifo_alloc( audio_out_ctx->sample_fmt, @@ -840,6 +931,7 @@ bool VideoStore::setup_resampler() { #if defined(HAVE_LIBAVRESAMPLE) // Setup the audio resampler resample_ctx = avresample_alloc_context(); + if ( !resample_ctx ) { Error("Could not allocate resample ctx"); av_frame_free(&in_frame); @@ -864,8 +956,7 @@ bool VideoStore::setup_resampler() { av_opt_set_int(resample_ctx, "out_channels", audio_out_ctx->channels, 0); - ret = avresample_open(resample_ctx); - if ( ret < 0 ) { + if ( (ret = avresample_open(resample_ctx)) < 0 ) { Error("Could not open resample ctx"); return false; } else { @@ -911,65 +1002,251 @@ bool VideoStore::setup_resampler() { #endif } // end bool VideoStore::setup_resampler() -int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { - av_init_packet(&opkt); - - dumpPacket(video_in_stream, ipkt, "video input packet"); - - opkt.flags = ipkt->flags; - opkt.data = ipkt->data; - opkt.size = ipkt->size; - opkt.duration = ipkt->duration; - - // Just because the in stream wraps, doesn't mean the out needs to. - // Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the out. - // So need to handle in wrap, without causing out wrap. - // The cameras that Icon has seem to do EOF instead of wrapping - - if ( ipkt->dts != AV_NOPTS_VALUE ) { - if ( !video_first_dts ) { - Debug(2, "Starting video first_dts will become %" PRId64, ipkt->dts); - video_first_dts = ipkt->dts; - } - opkt.dts = ipkt->dts - video_first_dts; - } else { - opkt.dts = next_dts[video_out_stream->index] ? av_rescale_q(next_dts[video_out_stream->index], video_out_stream->time_base, video_in_stream->time_base) : 0; - Debug(3, "Setting dts to video_next_dts %" PRId64 " from %" PRId64, opkt.dts, next_dts[video_out_stream->index]); +int VideoStore::writePacket(ZMPacket *ipkt) { + if ( ipkt->codec_type == AVMEDIA_TYPE_VIDEO ) { + return writeVideoFramePacket(ipkt); + } else if ( ipkt->codec_type == AVMEDIA_TYPE_AUDIO ) { + return writeAudioFramePacket(ipkt); } - if ( ipkt->pts != AV_NOPTS_VALUE ) { - opkt.pts = ipkt->pts - video_first_dts; - } else { - opkt.pts = AV_NOPTS_VALUE; - } - av_packet_rescale_ts(&opkt, video_in_stream->time_base, video_out_stream->time_base); - - dumpPacket(video_out_stream, &opkt, "after pts adjustment"); - write_packet(&opkt, video_out_stream); - - zm_av_packet_unref(&opkt); - + Error("Unknown stream type in packet (%d)", ipkt->codec_type); return 0; +} + +int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { + int ret; + frame_count += 1; + + // if we have to transcode + if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { + Debug(3, "Have encoding video frame count (%d)", frame_count); + + if (!zm_packet->out_frame) { + Debug(3, "Have no out frame. codec is %s sw_pf %d %s hw_pf %d %s", + chosen_codec_data->codec_name, + chosen_codec_data->sw_pix_fmt, av_get_pix_fmt_name(chosen_codec_data->sw_pix_fmt), + chosen_codec_data->hw_pix_fmt, av_get_pix_fmt_name(chosen_codec_data->hw_pix_fmt) + ); + AVFrame *out_frame = zm_packet->get_out_frame(video_out_ctx->width, video_out_ctx->height, chosen_codec_data->sw_pix_fmt); + if (!out_frame) { + Error("Unable to allocate a frame"); + return 0; + } + + if (zm_packet->image) { + Debug(2, "Have an image, convert it"); + //Go straight to out frame + swscale.Convert( + zm_packet->image, + zm_packet->buffer, + zm_packet->codec_imgsize, + zm_packet->image->AVPixFormat(), + chosen_codec_data->sw_pix_fmt, + video_out_ctx->width, + video_out_ctx->height + ); + } else if ( !zm_packet->in_frame ) { + Debug(4, "Have no in_frame"); + if (zm_packet->packet.size and !zm_packet->decoded) { + Debug(4, "Decoding"); + if ( !zm_packet->decode(video_in_ctx) ) { + Debug(2, "unable to decode yet."); + return 0; + } + // Go straight to out frame + swscale.Convert(zm_packet->in_frame, out_frame); + + } else { + Error("Have neither in_frame or image in packet %p %d!", + zm_packet, zm_packet->image_index); + return 0; + } // end if has packet or image + } else { + // Have in_frame.... may need to convert it to out_frame + swscale.Convert(zm_packet->in_frame, zm_packet->out_frame); + } // end if no in_frame + } // end if no out_frame + + AVFrame *frame = zm_packet->out_frame; + +#if HAVE_LIBAVUTIL_HWCONTEXT_H + if (video_out_ctx->hw_frames_ctx) { + if (!(hw_frame = av_frame_alloc())) { + ret = AVERROR(ENOMEM); + return ret; + } + if ((ret = av_hwframe_get_buffer(video_out_ctx->hw_frames_ctx, hw_frame, 0)) < 0) { + Error("Error code: %s", av_err2str(ret)); + av_frame_free(&hw_frame); + return ret; + } + if (!hw_frame->hw_frames_ctx) { + Error("Outof ram!"); + av_frame_free(&hw_frame); + return 0; + } + if ((ret = av_hwframe_transfer_data(hw_frame, zm_packet->out_frame, 0)) < 0) { + Error("Error while transferring frame data to surface: %s.", av_err2str(ret)); + av_frame_free(&hw_frame); + return ret; + } + + frame = hw_frame; + } // end if hwaccel +#endif + + //zm_packet->out_frame->coded_picture_number = frame_count; + //zm_packet->out_frame->display_picture_number = frame_count; + //zm_packet->out_frame->sample_aspect_ratio = (AVRational){ 0, 1 }; + // Do this to allow the encoder to choose whether to use I/P/B frame + //zm_packet->out_frame->pict_type = AV_PICTURE_TYPE_NONE; + //zm_packet->out_frame->key_frame = zm_packet->keyframe; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + frame->pkt_duration = 0; +#endif + + int64_t in_pts = zm_packet->timestamp->tv_sec * (uint64_t)1000000 + zm_packet->timestamp->tv_usec; + if ( !video_first_pts ) { + video_first_pts = in_pts; + Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%" PRIi64 ") usecs(%" PRIi64 ")", + video_first_pts, + static_cast(zm_packet->timestamp->tv_sec), + static_cast(zm_packet->timestamp->tv_usec)); + frame->pts = 0; + } else { + uint64_t useconds = in_pts - video_first_pts; + frame->pts = av_rescale_q(useconds, AV_TIME_BASE_Q, video_out_ctx->time_base); + Debug(2, + "Setting pts for frame(%d) to (%" PRId64 ") from (start %" PRIu64 " - %" PRIu64 " - secs(%" PRIi64 ") usecs(%" PRIi64 ") @ %d/%d", + frame_count, + frame->pts, + video_first_pts, + useconds, + static_cast(zm_packet->timestamp->tv_sec), + static_cast(zm_packet->timestamp->tv_usec), + video_out_ctx->time_base.num, + video_out_ctx->time_base.den); + } + + av_init_packet(&opkt); + opkt.data = nullptr; + opkt.size = 0; + + ret = zm_send_frame_receive_packet(video_out_ctx, frame, opkt); + if (ret <= 0) { + if (ret < 0) { + Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); + } + return ret; + } + ZM_DUMP_PACKET(opkt, "packet returned by codec"); + + // Need to adjust pts/dts values from codec time to stream time + if ( opkt.pts != AV_NOPTS_VALUE ) + opkt.pts = av_rescale_q(opkt.pts, video_out_ctx->time_base, video_out_stream->time_base); + if ( opkt.dts != AV_NOPTS_VALUE ) + opkt.dts = av_rescale_q(opkt.dts, video_out_ctx->time_base, video_out_stream->time_base); + Debug(1, "Timebase conversions using %d/%d -> %d/%d", + video_out_ctx->time_base.num, + video_out_ctx->time_base.den, + video_out_stream->time_base.num, + video_out_stream->time_base.den); + + + int64_t duration = 0; + if ( zm_packet->in_frame ) { + if ( zm_packet->in_frame->pkt_duration ) { + duration = av_rescale_q( + zm_packet->in_frame->pkt_duration, + video_in_stream->time_base, + video_out_stream->time_base); + Debug(1, "duration from ipkt: pts(%" PRId64 ") = pkt_duration(%" PRId64 ") => (%" PRId64 ") (%d/%d) (%d/%d)", + zm_packet->in_frame->pts, + zm_packet->in_frame->pkt_duration, + duration, + video_in_stream->time_base.num, + video_in_stream->time_base.den, + video_out_stream->time_base.num, + video_out_stream->time_base.den + ); + } else if ( video_last_pts != AV_NOPTS_VALUE ) { + duration = + av_rescale_q( + zm_packet->in_frame->pts - video_last_pts, + video_in_stream->time_base, + video_out_stream->time_base); + Debug(1, "duration calc: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ")", + zm_packet->in_frame->pts, + video_last_pts, + zm_packet->in_frame->pts - video_last_pts, + duration + ); + if ( duration <= 0 ) { + duration = zm_packet->in_frame->pkt_duration ? zm_packet->in_frame->pkt_duration : av_rescale_q(1, video_in_stream->time_base, video_out_stream->time_base); + } + } // end if in_frmae->pkt_duration + video_last_pts = zm_packet->in_frame->pts; + } else { + //duration = av_rescale_q(zm_packet->out_frame->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); + } // end if in_frmae + opkt.duration = duration; + + } else { // Passthrough + AVPacket *ipkt = &zm_packet->packet; + ZM_DUMP_STREAM_PACKET(video_in_stream, (*ipkt), "Doing passthrough, just copy packet"); + // Just copy it because the codec is the same + av_init_packet(&opkt); + opkt.data = ipkt->data; + opkt.size = ipkt->size; + opkt.flags = ipkt->flags; + opkt.duration = ipkt->duration; + + if ( ipkt->dts != AV_NOPTS_VALUE ) { + if ( !video_first_dts ) { + Debug(2, "Starting video first_dts will become %" PRId64, ipkt->dts); + video_first_dts = ipkt->dts; + } + opkt.dts = ipkt->dts - video_first_dts; + } else { + opkt.dts = next_dts[video_out_stream->index] ? av_rescale_q(next_dts[video_out_stream->index], video_out_stream->time_base, video_in_stream->time_base) : 0; + Debug(3, "Setting dts to video_next_dts %" PRId64 " from %" PRId64, opkt.dts, next_dts[video_out_stream->index]); + } + if ( ipkt->pts != AV_NOPTS_VALUE ) { + opkt.pts = ipkt->pts - video_first_dts; + } else { + opkt.pts = AV_NOPTS_VALUE; + } + + av_packet_rescale_ts(&opkt, video_in_stream->time_base, video_out_stream->time_base); + + ZM_DUMP_STREAM_PACKET(video_out_stream, opkt, "after pts adjustment"); + } // end if codec matches + + write_packet(&opkt, video_out_stream); + zm_av_packet_unref(&opkt); + if (hw_frame) av_frame_free(&hw_frame); + + return 1; } // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) -int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { +int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { + + AVPacket *ipkt = &zm_packet->packet; int ret; if ( !audio_out_stream ) { Debug(1, "Called writeAudioFramePacket when no audio_out_stream"); - return 0; // FIXME -ve return codes do not free packet in ffmpeg_camera at - // the moment + return 0; + // FIXME -ve return codes do not free packet in ffmpeg_camera at the moment } - dumpPacket(audio_in_stream, ipkt, "input packet"); + ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "input packet"); if ( !audio_first_dts ) { audio_first_dts = ipkt->dts; audio_next_pts = audio_out_ctx->frame_size; } + Debug(3, "audio first_dts to %" PRId64, audio_first_dts); // Need to adjust pts before feeding to decoder.... should really copy the pkt instead of modifying it - ipkt->pts -= audio_first_dts; - ipkt->dts -= audio_first_dts; - dumpPacket(audio_in_stream, ipkt, "after pts adjustment"); if ( audio_out_codec ) { // I wonder if we can get multiple frames per packet? Probably @@ -1017,16 +1294,18 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } // end while there is data in the resampler } else { - Debug(2,"copying"); av_init_packet(&opkt); opkt.data = ipkt->data; opkt.size = ipkt->size; opkt.flags = ipkt->flags; opkt.duration = ipkt->duration; - opkt.pts = ipkt->pts; - opkt.dts = ipkt->dts; + opkt.pts = ipkt->pts - audio_first_dts; + opkt.dts = ipkt->dts - audio_first_dts; + + ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "after pts adjustment"); av_packet_rescale_ts(&opkt, audio_in_stream->time_base, audio_out_stream->time_base); + ZM_DUMP_STREAM_PACKET(audio_out_stream, opkt, "after stream pts adjustment"); write_packet(&opkt, audio_out_stream); zm_av_packet_unref(&opkt); @@ -1045,7 +1324,7 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { } else if ( pkt->dts < stream->cur_dts ) { Debug(1, "non increasing dts, fixing. our dts %" PRId64 " stream cur_dts %" PRId64, pkt->dts, stream->cur_dts); pkt->dts = stream->cur_dts; - } + } if ( pkt->dts > pkt->pts ) { Debug(1, @@ -1055,16 +1334,17 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { pkt->pts = pkt->dts; } - dumpPacket(stream, pkt, "finished pkt"); - next_dts[stream->index] = opkt.dts + opkt.duration; - Debug(3, "video_next_dts has become %" PRId64, next_dts[stream->index]); + ZM_DUMP_STREAM_PACKET(stream, (*pkt), "finished pkt"); + next_dts[stream->index] = pkt->dts + pkt->duration; + Debug(3, "next_dts for stream %d has become %" PRId64, + stream->index, next_dts[stream->index]); int ret = av_interleaved_write_frame(oc, pkt); if ( ret != 0 ) { Error("Error writing packet: %s", av_make_error_string(ret).c_str()); } else { - Debug(2, "Success writing packet"); + Debug(4, "Success writing packet"); } return ret; } // end int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 64c2296ba..47872db2f 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -1,7 +1,11 @@ #ifndef ZM_VIDEOSTORE_H #define ZM_VIDEOSTORE_H +#include "zm_config.h" +#include "zm_define.h" #include "zm_ffmpeg.h" +#include "zm_swscale.h" + extern "C" { #ifdef HAVE_LIBSWRESAMPLE #include "libswresample/swresample.h" @@ -11,85 +15,115 @@ extern "C" { #endif #endif #include "libavutil/audio_fifo.h" +#if HAVE_LIBAVUTIL_HWCONTEXT_H + #include "libavutil/hwcontext.h" +#endif } #if HAVE_LIBAVCODEC -#include "zm_monitor.h" +class Monitor; +class ZMPacket; +class PacketQueue; class VideoStore { -private: - AVOutputFormat *out_format; - AVFormatContext *oc; + private: - AVCodec *video_out_codec; - AVCodecContext *video_out_ctx; - AVStream *video_out_stream; + struct CodecData { + const AVCodecID codec_id; + const char *codec_codec; + const char *codec_name; + const enum AVPixelFormat sw_pix_fmt; + const enum AVPixelFormat hw_pix_fmt; +#if HAVE_LIBAVUTIL_HWCONTEXT_H + const AVHWDeviceType hwdevice_type; +#endif + }; - AVStream *video_in_stream; + static struct CodecData codec_data[]; + CodecData *chosen_codec_data; - AVStream *audio_in_stream; - Monitor *monitor; + Monitor *monitor; + AVOutputFormat *out_format; + AVFormatContext *oc; + AVStream *video_out_stream; + AVStream *audio_out_stream; - // Move this into the object so that we aren't constantly allocating/deallocating it on the stack - AVPacket opkt; - // we are transcoding - AVFrame *in_frame; - AVFrame *out_frame; + AVCodec *video_out_codec; + AVCodecContext *video_in_ctx; + AVCodecContext *video_out_ctx; - AVCodecContext *video_in_ctx; - const AVCodec *audio_in_codec; - AVCodecContext *audio_in_ctx; + AVStream *video_in_stream; + AVStream *audio_in_stream; + + const AVCodec *audio_in_codec; + AVCodecContext *audio_in_ctx; + // The following are used when encoding the audio stream to AAC + AVCodec *audio_out_codec; + AVCodecContext *audio_out_ctx; + // Move this into the object so that we aren't constantly allocating/deallocating it on the stack + AVPacket opkt; + // we are transcoding + AVFrame *video_in_frame; + AVFrame *in_frame; + AVFrame *out_frame; + AVFrame *hw_frame; + + SWScale swscale; + unsigned int packets_written; + unsigned int frame_count; + + AVBufferRef *hw_device_ctx; - // The following are used when encoding the audio stream to AAC - AVStream *audio_out_stream; - AVCodec *audio_out_codec; - AVCodecContext *audio_out_ctx; #ifdef HAVE_LIBSWRESAMPLE - SwrContext *resample_ctx; + SwrContext *resample_ctx; + AVAudioFifo *fifo; #else #ifdef HAVE_LIBAVRESAMPLE - AVAudioResampleContext* resample_ctx; + AVAudioResampleContext* resample_ctx; #endif #endif - AVAudioFifo *fifo; - uint8_t *converted_in_samples; - - const char *filename; - const char *format; - - // These are for in - int64_t video_last_pts; - int64_t video_last_dts; - int64_t audio_last_pts; - int64_t audio_last_dts; + uint8_t *converted_in_samples; - int64_t video_first_pts; - int64_t video_first_dts; - int64_t audio_first_pts; - int64_t audio_first_dts; + const char *filename; + const char *format; - // These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big. - int64_t *next_dts; - int64_t audio_next_pts; + // These are for in + int64_t video_first_pts; + int64_t video_first_dts; + int64_t audio_first_pts; + int64_t audio_first_dts; + int64_t video_last_pts; + int64_t audio_last_pts; - int max_stream_index; + // These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big. + int64_t *next_dts; + int64_t audio_next_pts; - bool setup_resampler(); - int write_packet(AVPacket *pkt, AVStream *stream); + int max_stream_index; -public: - VideoStore( - const char *filename_in, - const char *format_in, - AVStream *video_in_stream, - AVStream *audio_in_stream, - Monitor * p_monitor); - bool open(); - ~VideoStore(); + bool setup_resampler(); + int write_packet(AVPacket *pkt, AVStream *stream); - int writeVideoFramePacket( AVPacket *pkt ); - int writeAudioFramePacket( AVPacket *pkt ); + public: + VideoStore( + const char *filename_in, + const char *format_in, + AVStream *video_in_stream, + AVCodecContext *video_in_ctx, + AVStream *audio_in_stream, + AVCodecContext *audio_in_ctx, + Monitor * p_monitor); + ~VideoStore(); + bool open(); + + void write_video_packet(AVPacket &pkt); + void write_audio_packet(AVPacket &pkt); + int writeVideoFramePacket(ZMPacket *pkt); + int writeAudioFramePacket(ZMPacket *pkt); + int writePacket(ZMPacket *pkt); + int write_packets(PacketQueue &queue); + void flush_codecs(); }; #endif //havelibav diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 9bb8d26e5..106ab2ce0 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -17,20 +17,13 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#define __STDC_FORMAT_MACROS 1 -#include -#include "zm.h" -#include "zm_db.h" #include "zm_zone.h" -#include "zm_image.h" -#include "zm_monitor.h" -#include "zm_fifo.h" +#include "zm_fifo.h" +#include "zm_fifo_debug.h" +#include "zm_monitor.h" void Zone::Setup( - Monitor *p_monitor, - int p_id, - const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, @@ -49,11 +42,6 @@ void Zone::Setup( int p_overload_frames, int p_extend_alarm_frames ) { - monitor = p_monitor; - - id = p_id; - label = new char[strlen(p_label)+1]; - strcpy(label, p_label); type = p_type; polygon = p_polygon; alarm_rgb = p_alarm_rgb; @@ -72,19 +60,13 @@ void Zone::Setup( overload_frames = p_overload_frames; extend_alarm_frames = p_extend_alarm_frames; - //Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); +#if 0 + Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", + id, label.c_str(), type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); +#endif - alarmed = false; - was_alarmed = false; - pixel_diff = 0; - alarm_pixels = 0; - alarm_filter_pixels = 0; - alarm_blob_pixels = 0; - alarm_blobs = 0; - min_blob_size = 0; - max_blob_size = 0; + ResetStats(); image = nullptr; - score = 0; overload_count = 0; extend_alarm_count = 0; @@ -112,36 +94,49 @@ void Zone::Setup( } } - if ( config.record_diag_images ) { - snprintf(diag_path, sizeof(diag_path), - config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", - monitor->getStorage()->Path(), id); - if ( config.record_diag_images_fifo ) - FifoStream::fifo_create_if_missing(diag_path); - pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); - } else { - diag_path[0] = 0; + if (config.record_diag_images) { + if (config.record_diag_images_fifo) { + diag_path = stringtf("%s/diagpipe-%d-poly.jpg", + staticConfig.PATH_SOCKS.c_str(), id); + + Fifo::fifo_create_if_missing(diag_path.c_str()); + } else { + diag_path = stringtf("%s/diag-%d-poly.jpg", + monitor->getStorage()->Path(), id); + } + pg_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); } } // end Zone::Setup Zone::~Zone() { - delete[] label; - delete image; + if ( image ) + delete image; delete pg_image; delete[] ranges; } void Zone::RecordStats(const Event *event) { static char sql[ZM_SQL_MED_BUFSIZ]; - db_mutex.lock(); snprintf(sql, sizeof(sql), - "INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", - monitor->Id(), id, event->Id(), event->Frames(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score + "INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, " + "PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, " + "Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, " + "MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", + monitor->Id(), id, event->Id(), event->Frames(), + stats.pixel_diff_, + stats.alarm_pixels_, + stats.alarm_filter_pixels_, + stats.alarm_blob_pixels_, + stats.alarm_blobs_, + stats.min_blob_size_, + stats.max_blob_size_, + stats.alarm_box_.LoX(), + stats.alarm_box_.LoY(), + stats.alarm_box_.HiX(), + stats.alarm_box_.HiY(), + stats.score_ ); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert event stats: %s", mysql_error(&dbconn)); - } - db_mutex.unlock(); + zmDbDo(sql); } // end void Zone::RecordStats( const Event *event ) bool Zone::CheckOverloadCount() { @@ -154,11 +149,12 @@ bool Zone::CheckOverloadCount() { } // end bool Zone::CheckOverloadCount() void Zone::SetScore(unsigned int nScore) { - score = nScore; + stats.score_ = nScore; } // end void Zone::SetScore(unsigned int nScore) void Zone::SetAlarmImage(const Image* srcImage) { - delete image; + if ( image ) + delete image; image = new Image(*srcImage); } // end void Zone::SetAlarmImage( const Image* srcImage ) @@ -199,13 +195,14 @@ bool Zone::CheckExtendAlarmCount() { bool Zone::CheckAlarms(const Image *delta_image) { ResetStats(); - if ( overload_count ) { + if (overload_count) { Info("In overload mode, %d frames of %d remaining", overload_count, overload_frames); overload_count--; return false; } - delete image; + if (image) + delete image; // Get the difference image Image *diff_image = image = new Image(*delta_image); int diff_width = diff_image->Width(); @@ -227,34 +224,34 @@ bool Zone::CheckAlarms(const Image *delta_image) { unsigned int hi_x = polygon.HiX(); unsigned int hi_y = polygon.HiY(); - Debug(4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y); + Debug(4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label.c_str(), lo_y, hi_y); /* if(config.cpu_extensions && sse_version >= 20) { sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); } else { std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); } */ - std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + std_alarmedpixels(diff_image, pg_image, &stats.alarm_pixels_, &pixel_diff_count); - if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + if (config.record_diag_images) + diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); - if ( pixel_diff_count && alarm_pixels ) - pixel_diff = pixel_diff_count/alarm_pixels; + if (pixel_diff_count && stats.alarm_pixels_) + stats.pixel_diff_ = pixel_diff_count/stats.alarm_pixels_; Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", - alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff); + stats.alarm_pixels_, min_alarm_pixels, max_alarm_pixels, stats.pixel_diff_); - if ( config.record_diag_images_fifo ) { + if (config.record_diag_images_fifo) { FifoDebug(5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}", - id,alarm_pixels, pixel_diff); + id, stats.alarm_pixels_, stats.pixel_diff_); } - if ( alarm_pixels ) { - if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) { + if (stats.alarm_pixels_) { + if (min_alarm_pixels && (stats.alarm_pixels_ < (unsigned int)min_alarm_pixels)) { /* Not enough pixels alarmed */ return false; - } else if ( max_alarm_pixels && (alarm_pixels > (unsigned int)max_alarm_pixels) ) { + } else if (max_alarm_pixels && (stats.alarm_pixels_ > (unsigned int)max_alarm_pixels)) { /* Too many pixels alarmed */ overload_count = overload_frames; return false; @@ -264,146 +261,136 @@ bool Zone::CheckAlarms(const Image *delta_image) { return false; } - if ( max_alarm_pixels != 0 ) - score = (100*alarm_pixels)/max_alarm_pixels; - else - score = (100*alarm_pixels)/polygon.Area(); - - if ( score < 1 ) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug(5, "Current score is %d", score); + stats.score_ = (100*stats.alarm_pixels_)/(max_alarm_pixels ? max_alarm_pixels : polygon.Area()); + if (stats.score_ < 1) + stats.score_ = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug(5, "Current score is %d", stats.score_); - if ( check_method >= FILTERED_PIXELS ) { + if (check_method >= FILTERED_PIXELS) { int bx = filter_box.X(); int by = filter_box.Y(); int bx1 = bx-1; int by1 = by-1; Debug(5, "Checking for filtered pixels"); - if ( bx > 1 || by > 1 ) { + if (bx > 1 || by > 1) { // Now remove any pixels smaller than our filter size unsigned char *cpdiff; int ldx, hdx, ldy, hdy; bool block; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + for (unsigned int y = lo_y; y <= hi_y; y++) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; pdiff = (uint8_t*)diff_image->Buffer(lo_x, y); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { - if ( *pdiff == WHITE ) { + for (int x = lo_x; x <= hi_x; x++, pdiff++) { + if (*pdiff == kWhite) { // Check participation in an X block ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); ldy = (y>=(lo_y+by1))?-by1:lo_y-y; hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); block = false; - for ( int dy = ldy; !block && dy <= hdy; dy++ ) { - for ( int dx = ldx; !block && dx <= hdx; dx++ ) { + for (int dy = ldy; !block && dy <= hdy; dy++) { + for (int dx = ldx; !block && dx <= hdx; dx++) { block = true; - for ( int dy2 = 0; block && dy2 < by; dy2++ ) { - for ( int dx2 = 0; block && dx2 < bx; dx2++ ) { + for (int dy2 = 0; block && dy2 < by; dy2++) { + for (int dx2 = 0; block && dx2 < bx; dx2++) { cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); - if ( !*cpdiff ) { + if (!*cpdiff ) { block = false; } } } } } - if ( !block ) { - *pdiff = BLACK; + if (!block) { + *pdiff = kBlack; continue; } - alarm_filter_pixels++; - } // end if white - } // end for x - } // end foreach y line + stats.alarm_filter_pixels_++; + } // end if white + } // end for x + } // end foreach y line } else { - alarm_filter_pixels = alarm_pixels; + stats.alarm_filter_pixels_ = stats.alarm_pixels_; } - if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + if (config.record_diag_images) + diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); Debug(5, "Got %d filtered pixels, need %d -> %d", - alarm_filter_pixels, min_filter_pixels, max_filter_pixels); + stats.alarm_filter_pixels_, min_filter_pixels, max_filter_pixels); - if ( config.record_diag_images_fifo ) - FifoDebug(5, "{\"zone\":%d,\"type\":\"FILT\",\"pixels\":%d}", id, alarm_filter_pixels); + if (config.record_diag_images_fifo) + FifoDebug(5, "{\"zone\":%d,\"type\":\"FILT\",\"pixels\":%d}", id, stats.alarm_filter_pixels_); - if ( alarm_filter_pixels ) { - if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { + if (stats.alarm_filter_pixels_) { + if (min_filter_pixels && (stats.alarm_filter_pixels_ < min_filter_pixels)) { /* Not enough pixels alarmed */ + stats.score_ = 0; return false; - } else if ( max_filter_pixels && (alarm_filter_pixels > max_filter_pixels) ) { + } else if (max_filter_pixels && (stats.alarm_filter_pixels_ > max_filter_pixels)) { /* Too many pixels alarmed */ overload_count = overload_frames; + stats.score_ = 0; return false; } } else { /* No filtered pixels */ + stats.score_ = 0; return false; } - if ( max_filter_pixels != 0 ) - score = (100*alarm_filter_pixels)/max_filter_pixels; - else - score = (100*alarm_filter_pixels)/polygon.Area(); + if (max_filter_pixels != 0) + stats.score_ = (100*stats.alarm_filter_pixels_)/max_filter_pixels; + else + stats.score_ = (100*stats.alarm_filter_pixels_)/polygon.Area(); - if ( score < 1 ) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug(5, "Current score is %d", score); + if (stats.score_ < 1) + stats.score_ = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug(5, "Current score is %d", stats.score_); - if ( check_method >= BLOBS ) { + if (check_method >= BLOBS) { Debug(5, "Checking for blob pixels"); - typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; - BlobStats blob_stats[256]; + // ICON FIXME Would like to get rid of this memset memset(blob_stats, 0, sizeof(BlobStats)*256); uint8_t *spdiff; uint8_t last_x, last_y; BlobStats *bsx, *bsy; BlobStats *bsm, *bss; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + for (unsigned int y = lo_y; y <= hi_y; y++) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; - pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { - if ( *pdiff == WHITE ) { + pdiff = (uint8_t*)diff_image->Buffer(lo_x, y); + for (int x = lo_x; x <= hi_x; x++, pdiff++) { + if (*pdiff == kWhite) { Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff); - //last_x = (x>lo_x)?*(pdiff-1):0; - //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; - - last_x = 0; - if ( x > 0 ) { - if ( (x-1) >= lo_x ) { - last_x = *(pdiff-1); - } - } + last_x = ((x > 0) && ( (x-1) >= lo_x )) ? *(pdiff-1) : 0; last_y = 0; - if (y > 0 ) { + if ( y > 0 ) { if ( (y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x ) { last_y = *(pdiff-diff_width); } } - if ( last_x ) { + if (last_x) { Debug(9, "Left neighbour is %d", last_x); bsx = &blob_stats[last_x]; - if ( last_y ) { + if (last_y) { Debug(9, "Top neighbour is %d", last_y); bsy = &blob_stats[last_y]; - if ( last_x == last_y ) { + if (last_x == last_y) { Debug(9, "Matching neighbours, setting to %d", last_x); // Add to the blob from the x side (either side really) *pdiff = last_x; - alarm_blob_pixels++; + stats.alarm_blob_pixels_++; bsx->count++; - if ( x > bsx->hi_x ) bsx->hi_x = x; - if ( (int)y > bsx->hi_y ) bsx->hi_y = y; + if (x > bsx->hi_x) bsx->hi_x = x; + if ((int)y > bsx->hi_y) bsx->hi_y = y; } else { // Aggregate blobs bsm = bsx->count>=bsy->count?bsx:bsy; @@ -419,7 +406,7 @@ bool Zone::CheckAlarms(const Image *delta_image) { ); // Now change all those pixels to the other setting int changed = 0; - for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++ ) { + for (int sy = bss->lo_y; sy <= bss->hi_y; sy++) { int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x; int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x; @@ -428,18 +415,18 @@ bool Zone::CheckAlarms(const Image *delta_image) { sy, lo_sx, hi_sx, ranges[sy].lo_x, ranges[sy].hi_x ); spdiff = diff_buff + ((diff_width * sy) + lo_sx); - for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) { + for (int sx = lo_sx; sx <= hi_sx; sx++, spdiff++) { Debug(9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff); - if ( *spdiff == bss->tag ) { + if (*spdiff == bss->tag) { Debug(9, "Setting pixel"); *spdiff = bsm->tag; changed++; } } - } + } // end for sy = lo_y .. hi_y *pdiff = bsm->tag; - alarm_blob_pixels++; - if ( !changed ) { + stats.alarm_blob_pixels_++; + if (!changed) { Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d\n" "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", @@ -452,17 +439,17 @@ bool Zone::CheckAlarms(const Image *delta_image) { // Merge the slave blob into the master bsm->count += bss->count+1; - if ( x > bsm->hi_x ) bsm->hi_x = x; - if ( (int)y > bsm->hi_y ) bsm->hi_y = y; - if ( bss->lo_x < bsm->lo_x ) bsm->lo_x = bss->lo_x; - if ( bss->lo_y < bsm->lo_y ) bsm->lo_y = bss->lo_y; - if ( bss->hi_x > bsm->hi_x ) bsm->hi_x = bss->hi_x; - if ( bss->hi_y > bsm->hi_y ) bsm->hi_y = bss->hi_y; + if (x > bsm->hi_x) bsm->hi_x = x; + if ((int)y > bsm->hi_y) bsm->hi_y = y; + if (bss->lo_x < bsm->lo_x) bsm->lo_x = bss->lo_x; + if (bss->lo_y < bsm->lo_y) bsm->lo_y = bss->lo_y; + if (bss->hi_x > bsm->hi_x) bsm->hi_x = bss->hi_x; + if (bss->hi_y > bsm->hi_y) bsm->hi_y = bss->hi_y; - alarm_blobs--; + stats.alarm_blobs_--; Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs", - bss->tag, bsm->tag, x, y, alarm_blobs); + bss->tag, bsm->tag, x, y, stats.alarm_blobs_); // Clear out the old blob bss->tag = 0; @@ -476,50 +463,50 @@ bool Zone::CheckAlarms(const Image *delta_image) { Debug(9, "Setting to left neighbour %d", last_x); // Add to the blob from the x side *pdiff = last_x; - alarm_blob_pixels++; + stats.alarm_blob_pixels_++; bsx->count++; - if ( x > bsx->hi_x ) bsx->hi_x = x; - if ( (int)y > bsx->hi_y ) bsx->hi_y = y; + if (x > bsx->hi_x) bsx->hi_x = x; + if ((int)y > bsx->hi_y) bsx->hi_y = y; } } else { - if ( last_y ) { + if (last_y) { Debug(9, "Top neighbour is %d", last_y); // Add to the blob from the y side BlobStats *bsy = &blob_stats[last_y]; *pdiff = last_y; - alarm_blob_pixels++; + stats.alarm_blob_pixels_++; bsy->count++; - if ( x > bsy->hi_x ) bsy->hi_x = x; - if ( (int)y > bsy->hi_y ) bsy->hi_y = y; + if (x > bsy->hi_x) bsy->hi_x = x; + if ((int)y > bsy->hi_y) bsy->hi_y = y; } else { // Create a new blob int i; - for ( i = (WHITE-1); i > 0; i-- ) { + for (i = (kWhite-1); i > 0; i--) { BlobStats *bs = &blob_stats[i]; // See if we can recycle one first, only if it's at least two rows up - if ( bs->count && bs->hi_y < (int)(y-1) ) { + if (bs->count && bs->hi_y < (int)(y-1)) { if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { - if ( ( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images ) { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { + if (( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images) { + for (int sy = bs->lo_y; sy <= bs->hi_y; sy++) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { - if ( *spdiff == bs->tag ) { - *spdiff = BLACK; + for (int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++) { + if (*spdiff == bs->tag) { + *spdiff = kBlack; } } } } - alarm_blobs--; - alarm_blob_pixels -= bs->count; + stats.alarm_blobs_--; + stats.alarm_blob_pixels_ -= bs->count; Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", - i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs); + i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, stats.alarm_blobs_); bs->tag = 0; bs->count = 0; @@ -529,21 +516,21 @@ bool Zone::CheckAlarms(const Image *delta_image) { bs->hi_y = 0; } } - if ( !bs->count ) { + if (!bs->count) { Debug(9, "Creating new blob %d", i); *pdiff = i; - alarm_blob_pixels++; + stats.alarm_blob_pixels_++; bs->tag = i; bs->count++; bs->lo_x = bs->hi_x = x; bs->lo_y = bs->hi_y = y; - alarm_blobs++; + stats.alarm_blobs_++; - Debug(6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, alarm_blobs); + Debug(6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, stats.alarm_blobs_); break; } } - if ( i == 0 ) { + if (i == 0) { Warning("Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive."); x = hi_x+1; y = hi_y+1; @@ -554,41 +541,42 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + if (config.record_diag_images) + diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); - if ( !alarm_blobs ) { + if (!stats.alarm_blobs_) { + stats.score_ = 0; return false; } Debug(5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", - alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); + stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); - if ( config.record_diag_images_fifo ) { + if (config.record_diag_images_fifo) { FifoDebug(5, "{\"zone\":%d,\"type\":\"RBLB\",\"pixels\":%d,\"blobs\":%d}", - id, alarm_blob_pixels, alarm_blobs); + id, stats.alarm_blob_pixels_, stats.alarm_blobs_); } // Now eliminate blobs under the threshold - for ( int i = 1; i < WHITE; i++ ) { + for (uint32 i = 1; i < kWhite; i++) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { - if ( ( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images ) { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { + if (bs->count) { + if ((min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels)) { + if (( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images) { + for (int sy = bs->lo_y; sy <= bs->hi_y; sy++) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { - if ( *spdiff == bs->tag ) { - *spdiff = BLACK; + for (int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++) { + if (*spdiff == bs->tag) { + *spdiff = kBlack; } } } } - alarm_blobs--; - alarm_blob_pixels -= bs->count; + stats.alarm_blobs_--; + stats.alarm_blob_pixels_ -= bs->count; Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", - i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs); + i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, stats.alarm_blobs_); bs->tag = 0; bs->count = 0; @@ -598,69 +586,72 @@ bool Zone::CheckAlarms(const Image *delta_image) { bs->hi_y = 0; } else { Debug(6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", - i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs); - if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count; - if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count; + i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, stats.alarm_blobs_); + if (!stats.min_blob_size_ || bs->count < stats.min_blob_size_) stats.min_blob_size_ = bs->count; + if (!stats.max_blob_size_ || bs->count > stats.max_blob_size_) stats.max_blob_size_ = bs->count; } } // end if bs_count } // end for i < WHITE - if ( config.record_diag_images ) - diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); + if (config.record_diag_images) + diff_image->WriteJpeg(diag_path.c_str(), config.record_diag_images_fifo); Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", - alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); + stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); - if ( config.record_diag_images_fifo ) { + if (config.record_diag_images_fifo) { FifoDebug(5, "{\"zone\":%d,\"type\":\"FBLB\",\"pixels\":%d,\"blobs\":%d}", - id, alarm_blob_pixels, alarm_blobs); + id, stats.alarm_blob_pixels_, stats.alarm_blobs_); } - if ( alarm_blobs ) { - if ( min_blobs && (alarm_blobs < min_blobs) ) { + if (stats.alarm_blobs_) { + if (min_blobs && (stats.alarm_blobs_ < min_blobs)) { /* Not enough pixels alarmed */ + stats.score_ = 0; return false; - } else if ( max_blobs && (alarm_blobs > max_blobs) ) { + } else if (max_blobs && (stats.alarm_blobs_ > max_blobs)) { /* Too many pixels alarmed */ overload_count = overload_frames; + stats.score_ = 0; return false; } } else { /* No blobs */ + stats.score_ = 0; return false; } - if ( max_blob_pixels != 0 ) - score = (100*alarm_blob_pixels)/(max_blob_pixels); + if (max_blob_pixels != 0) + stats.score_ = (100*stats.alarm_blob_pixels_)/max_blob_pixels; else - score = (100*alarm_blob_pixels)/polygon.Area(); + stats.score_ = (100*stats.alarm_blob_pixels_)/polygon.Area(); - if ( score < 1 ) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug(5, "Current score is %d", score); + if (stats.score_ < 1) + stats.score_ = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug(5, "Current score is %d", stats.score_); alarm_lo_x = polygon.HiX()+1; alarm_hi_x = polygon.LoX()-1; alarm_lo_y = polygon.HiY()+1; alarm_hi_y = polygon.LoY()-1; - for ( int i = 1; i < WHITE; i++ ) { + for (uint32 i = 1; i < kWhite; i++) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) { - if ( bs->count == max_blob_size ) { - if ( config.weighted_alarm_centres ) { + if (bs->count) { + if (bs->count == stats.max_blob_size_) { + if (config.weighted_alarm_centres) { unsigned long x_total = 0; unsigned long y_total = 0; - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { + for (int sy = bs->lo_y; sy <= bs->hi_y; sy++) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { - if ( *spdiff == bs->tag ) { + for (int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++) { + if (*spdiff == bs->tag) { x_total += sx; y_total += sy; } } - } + } // end for sy = lo_y .. hi_y alarm_mid_x = int(round(x_total/bs->count)); alarm_mid_y = int(round(y_total/bs->count)); } else { @@ -669,10 +660,10 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - if ( alarm_lo_x > bs->lo_x ) alarm_lo_x = bs->lo_x; - if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y; - if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x; - if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y; + if (alarm_lo_x > bs->lo_x) alarm_lo_x = bs->lo_x; + if (alarm_lo_y > bs->lo_y) alarm_lo_y = bs->lo_y; + if (alarm_hi_x < bs->hi_x) alarm_hi_x = bs->hi_x; + if (alarm_hi_y < bs->hi_y) alarm_hi_y = bs->hi_y; } // end if bs->count } // end for i < WHITE } else { @@ -681,64 +672,64 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - if ( type == INCLUSIVE ) { + if (type == INCLUSIVE) { // score >>= 1; - score /= 2; - } else if ( type == EXCLUSIVE ) { - // score <<= 1; - score *= 2; + stats.score_ /= 2; + } else if (type == EXCLUSIVE) { + // stats.score <<= 1; + stats.score_ *= 2; } - Debug(5, "Adjusted score is %d", score); + Debug(5, "Adjusted score is %d", stats.score_); // Now outline the changed region - if ( score ) { - alarm_box = Box(Coord(alarm_lo_x, alarm_lo_y), Coord(alarm_hi_x, alarm_hi_y)); + if (stats.score_) { + stats.alarm_box_ = Box(Coord(alarm_lo_x, alarm_lo_y), Coord(alarm_hi_x, alarm_hi_y)); //if ( monitor->followMotion() ) if ( true ) { - alarm_centre = Coord(alarm_mid_x, alarm_mid_y); + stats.alarm_centre_ = Coord(alarm_mid_x, alarm_mid_y); } else { - alarm_centre = alarm_box.Centre(); + stats.alarm_centre_ = stats.alarm_box_.Centre(); } - if ( (type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1) ) { + if ((type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1)) { // First mask out anything we don't want - for ( unsigned int y = lo_y; y <= hi_y; y++ ) { + for (unsigned int y = lo_y; y <= hi_y; y++) { pdiff = diff_buff + ((diff_width * y) + lo_x); int lo_x2 = ranges[y].lo_x; int hi_x2 = ranges[y].hi_x; int lo_gap = lo_x2-lo_x; - if ( lo_gap > 0 ) { - if ( lo_gap == 1 ) { - *pdiff++ = BLACK; + if (lo_gap > 0) { + if (lo_gap == 1) { + *pdiff++ = kBlack; } else { - memset(pdiff, BLACK, lo_gap); + memset(pdiff, kBlack, lo_gap); pdiff += lo_gap; } } const uint8_t* ppoly = pg_image->Buffer(lo_x2, y); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) { - if ( !*ppoly ) { - *pdiff = BLACK; + for (int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++) { + if (!*ppoly) { + *pdiff = kBlack; } } int hi_gap = hi_x-hi_x2; - if ( hi_gap > 0 ) { - if ( hi_gap == 1 ) { - *pdiff = BLACK; + if (hi_gap > 0) { + if (hi_gap == 1) { + *pdiff = kBlack; } else { - memset(pdiff, BLACK, hi_gap); + memset(pdiff, kBlack, hi_gap); } } - } // end for y + } // end for y - if ( monitor->Colours() == ZM_COLOUR_GRAY8 ) { + if (monitor->Colours() == ZM_COLOUR_GRAY8) { image = diff_image->HighlightEdges(alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent()); } else { image = diff_image->HighlightEdges(alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent()); @@ -746,79 +737,49 @@ bool Zone::CheckAlarms(const Image *delta_image) { // Only need to delete this when 'image' becomes detached and points somewhere else delete diff_image; - } else { - delete image; - image = 0; - } + diff_image = nullptr; + } // end if ( (type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1) Debug(1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", - Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score); - } + Label(), stats.pixel_diff_, stats.alarm_pixels_, stats.alarm_filter_pixels_, stats.alarm_blob_pixels_, stats.alarm_blobs_, stats.score_); + } // end if score return true; } bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) { - Debug(3, "Parsing polygon string '%s'", poly_string); - - char *str_ptr = new char[strlen(poly_string)+1]; - char *str = str_ptr; - strcpy(str, poly_string); - - char *ws; + char *str = (char *)poly_string; int n_coords = 0; int max_n_coords = strlen(str)/4; Coord *coords = new Coord[max_n_coords]; - while( true ) { - if ( *str == '\0' ) { - break; - } - ws = strchr(str, ' '); - if ( ws ) { - *ws = '\0'; - } + while (*str != '\0') { char *cp = strchr(str, ','); - if ( !cp ) { + if (!cp) { Error("Bogus coordinate %s found in polygon string", str); - delete[] coords; - delete[] str_ptr; - return false; - } else { - *cp = '\0'; - char *xp = str; - char *yp = cp+1; + break; + } + int x = atoi(str); + int y = atoi(cp+1); + Debug(3, "Got coordinate %d,%d from polygon string", x, y); + coords[n_coords++] = Coord(x, y); - int x = atoi(xp); - int y = atoi(yp); - - Debug(3, "Got coordinate %d,%d from polygon string", x, y); -#if 0 - if ( x < 0 ) - x = 0; - else if ( x >= width ) - x = width-1; - if ( y < 0 ) - y = 0; - else if ( y >= height ) - y = height-1; -#endif - coords[n_coords++] = Coord( x, y ); - } - if ( ws ) + char *ws = strchr(cp+2, ' '); + if (ws) str = ws+1; else break; - } - polygon = Polygon(n_coords, coords); + } // end while ! end of string - Debug(3, "Successfully parsed polygon string"); - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); + if (n_coords > 2) { + Debug(3, "Successfully parsed polygon string %s", str); + polygon = Polygon(n_coords, coords); + } else { + Error("Not enough coordinates to form a polygon!"); + n_coords = 0; + } delete[] coords; - delete[] str_ptr; - - return true; -} + return n_coords ? true : false; +} // end bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) { Debug(3, "Parsing zone string '%s'", zone_string); @@ -827,13 +788,12 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P char *str = str_ptr; strcpy(str, zone_string); + zone_id = strtol(str, 0, 10); + Debug(3, "Got zone %d from zone string", zone_id); + char *ws = strchr(str, ' '); if ( !ws ) { Debug(3, "No initial whitespace found in zone string '%s', finishing", str); - } - zone_id = strtol(str, 0, 10); - Debug(3, "Got zone %d from zone string", zone_id); - if ( !ws ) { delete[] str_ptr; return true; } @@ -841,13 +801,11 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P *ws = '\0'; str = ws+1; + colour = strtol(str, 0, 16); + Debug(3, "Got colour %06x from zone string", colour); ws = strchr(str, ' '); if ( !ws ) { Debug(3, "No secondary whitespace found in zone string '%s', finishing", zone_string); - } - colour = strtol(str, 0, 16); - Debug(3, "Got colour %06x from zone string", colour); - if ( !ws ) { delete[] str_ptr; return true; } @@ -855,47 +813,40 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P str = ws+1; bool result = ParsePolygonString(str, polygon); - - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); - delete[] str_ptr; return result; -} +} // end bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) -int Zone::Load(Monitor *monitor, Zone **&zones) { - static char sql[ZM_SQL_MED_BUFSIZ]; +std::vector Zone::Load(Monitor *monitor) { + std::vector zones; - db_mutex.lock(); - snprintf(sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - return 0; + std::string sql = stringtf("SELECT Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0," + "MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels," + "FilterX,FilterY,MinFilterPixels,MaxFilterPixels," + "MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs," + "OverloadFrames,ExtendAlarmFrames" + " FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id()); + + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) { + return {}; } - MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - db_mutex.unlock(); - return 0; - } - db_mutex.unlock(); - int n_zones = mysql_num_rows(result); + uint32 n_zones = mysql_num_rows(result); Debug(1, "Got %d zones for monitor %s", n_zones, monitor->Name()); - delete[] zones; - zones = new Zone *[n_zones]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + + zones.reserve(n_zones); + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { int col = 0; int Id = atoi(dbrow[col++]); const char *Name = dbrow[col++]; - int Type = atoi(dbrow[col++]); + ZoneType Type = static_cast(atoi(dbrow[col++])); const char *Units = dbrow[col++]; const char *Coords = dbrow[col++]; int AlarmRGB = dbrow[col]?atoi(dbrow[col]):0; col++; - int CheckMethod = atoi(dbrow[col++]); + Zone::CheckMethod CheckMethod = static_cast(atoi(dbrow[col++])); int MinPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; @@ -918,16 +869,17 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { Polygon polygon; if ( !ParsePolygonString(Coords, polygon) ) { Error("Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name()); - n_zones -= 1; continue; } if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { - Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", + Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY()); - n_zones -= 1; - continue; + if ( polygon.LoX() < 0 ) polygon.LoX(0); + if ( polygon.HiX() >= (int)monitor->Width()) polygon.HiX((int)monitor->Width()); + if ( polygon.LoY() < 0 ) polygon.LoY(0); + if ( polygon.HiY() >= (int)monitor->Height() ) polygon.HiY((int)monitor->Height()); } if ( false && !strcmp( Units, "Percent" ) ) { @@ -939,23 +891,30 @@ int Zone::Load(Monitor *monitor, Zone **&zones) { MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; } - if ( atoi(dbrow[2]) == Zone::INACTIVE ) { - zones[i] = new Zone(monitor, Id, Name, polygon); - } else if ( atoi(dbrow[2]) == Zone::PRIVACY ) { - zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon); + if (atoi(dbrow[2]) == Zone::INACTIVE) { + zones.emplace_back(monitor, Id, Name, polygon); + } else if (atoi(dbrow[2]) == Zone::PRIVACY) { + zones.emplace_back(monitor, Id, Name, Type, polygon); + } else { + zones.emplace_back( + monitor, Id, Name, Type, polygon, AlarmRGB, + CheckMethod, MinPixelThreshold, MaxPixelThreshold, + MinAlarmPixels, MaxAlarmPixels, Coord(FilterX, FilterY), + MinFilterPixels, MaxFilterPixels, + MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, + OverloadFrames, ExtendAlarmFrames); } - zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames); } // end foreach row mysql_free_result(result); - return n_zones; -} // end int Zone::Load(Monitor *monitor, Zone **&zones) + return zones; +} // end std::vector Zone::Load(Monitor *monitor) -bool Zone::DumpSettings(char *output, bool /*verbose*/) { +bool Zone::DumpSettings(char *output, bool /*verbose*/) const { output[0] = 0; - sprintf( output+strlen(output), " Id : %d\n", id ); - sprintf( output+strlen(output), " Label : %s\n", label ); - sprintf( output+strlen(output), " Type: %d - %s\n", type, + sprintf(output+strlen(output), " Id : %d\n", id ); + sprintf(output+strlen(output), " Label : %s\n", label.c_str() ); + sprintf(output+strlen(output), " Type: %d - %s\n", type, type==ACTIVE?"Active":( type==INCLUSIVE?"Inclusive":( type==EXCLUSIVE?"Exclusive":( @@ -1015,15 +974,53 @@ void Zone::std_alarmedpixels( if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) { pixelsalarmed++; pixelsdifference += *pdiff; - *pdiff = WHITE; + *pdiff = kWhite; } else { - *pdiff = BLACK; + *pdiff = kBlack; } } - } + } // end for y = lo_y to hi_y /* Store the results */ *pixel_count = pixelsalarmed; *pixel_sum = pixelsdifference; Debug(7, "STORED pixelsalarmed(%d), pixelsdifference(%d)", pixelsalarmed, pixelsdifference); +} // end void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum) + +Zone::Zone(const Zone &z) : + monitor(z.monitor), + id(z.id), + label(z.label), + type(z.type), + polygon(z.polygon), + alarm_rgb(z.alarm_rgb), + check_method(z.check_method), + min_pixel_threshold(z.min_pixel_threshold), + max_pixel_threshold(z.max_pixel_threshold), + min_alarm_pixels(z.min_alarm_pixels), + max_alarm_pixels(z.max_alarm_pixels), + filter_box(z.filter_box), + min_filter_pixels(z.min_filter_pixels), + max_filter_pixels(z.max_filter_pixels), + min_blob_pixels(z.min_blob_pixels), + max_blob_pixels(z.max_blob_pixels), + min_blobs(z.min_blobs), + max_blobs(z.max_blobs), + overload_frames(z.overload_frames), + extend_alarm_frames(z.extend_alarm_frames), + alarmed(z.alarmed), + was_alarmed(z.was_alarmed), + stats(z.stats), + overload_count(z.overload_count), + extend_alarm_count(z.extend_alarm_count), + diag_path(z.diag_path) +{ + std::copy(z.blob_stats, z.blob_stats+256, blob_stats); + pg_image = z.pg_image ? new Image(*z.pg_image) : nullptr; + ranges = new Range[monitor->Height()]; + std::copy(z.ranges, z.ranges+monitor->Height(), ranges); + image = z.image ? new Image(*z.image) : nullptr; + //z.stats.debug("Copy Source"); + stats.DumpToLog("Copy dest"); } + diff --git a/src/zm_zone.h b/src/zm_zone.h index 0c7b26a57..30f7e29eb 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -20,160 +20,216 @@ #ifndef ZM_ZONE_H #define ZM_ZONE_H -#include "zm_rgb.h" +#include "zm_box.h" #include "zm_coord.h" +#include "zm_define.h" +#include "zm_config.h" #include "zm_poly.h" -#include "zm_image.h" -#include "zm_event.h" +#include "zm_rgb.h" +#include "zm_zone_stats.h" +#include +#include +#include + +class Event; +class Image; class Monitor; // // This describes a 'zone', or an area of an image that has certain // detection characteristics. // -class Zone -{ -protected: - struct Range - { - int lo_x; - int hi_x; - int off_x; - }; -public: - typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, PRECLUSIVE, INACTIVE, PRIVACY } ZoneType; - typedef enum { ALARMED_PIXELS=1, FILTERED_PIXELS, BLOBS } CheckMethod; +class Zone { + protected: + struct Range { + int lo_x; + int hi_x; + int off_x; + }; + typedef struct { + unsigned char tag; + int count; + int lo_x; + int hi_x; + int lo_y; + int hi_y; + } BlobStats; + public: + typedef enum { ACTIVE=1, INCLUSIVE, EXCLUSIVE, PRECLUSIVE, INACTIVE, PRIVACY } ZoneType; + typedef enum { ALARMED_PIXELS=1, FILTERED_PIXELS, BLOBS } CheckMethod; -protected: - // Inputs - Monitor *monitor; + protected: + // Inputs + Monitor *monitor; - int id; - char *label; - ZoneType type; - Polygon polygon; - Rgb alarm_rgb; - CheckMethod check_method; + int id; + std::string label; + ZoneType type; + Polygon polygon; + Rgb alarm_rgb; + CheckMethod check_method; - int min_pixel_threshold; - int max_pixel_threshold; + int min_pixel_threshold; + int max_pixel_threshold; - int min_alarm_pixels; - int max_alarm_pixels; + int min_alarm_pixels; + int max_alarm_pixels; - Coord filter_box; - int min_filter_pixels; - int max_filter_pixels; + Coord filter_box; + int min_filter_pixels; + int max_filter_pixels; - int min_blob_pixels; - int max_blob_pixels; - int min_blobs; - int max_blobs; + BlobStats blob_stats[256]; + int min_blob_pixels; + int max_blob_pixels; + int min_blobs; + int max_blobs; - int overload_frames; - int extend_alarm_frames; + int overload_frames; + int extend_alarm_frames; - // Outputs/Statistics - bool alarmed; - bool was_alarmed; - int pixel_diff; - unsigned int alarm_pixels; - int alarm_filter_pixels; - int alarm_blob_pixels; - int alarm_blobs; - int min_blob_size; - int max_blob_size; - Box alarm_box; - Coord alarm_centre; - unsigned int score; - Image *pg_image; - Range *ranges; - Image *image; + // Outputs/Statistics + bool alarmed; + bool was_alarmed; + ZoneStats stats; + Image *pg_image; + Range *ranges; + Image *image; - int overload_count; - int extend_alarm_count; - char diag_path[PATH_MAX]; + int overload_count; + int extend_alarm_count; + std::string diag_path; -protected: - void Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs, int p_overload_frames, int p_extend_alarm_frames ); - void std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum); - -public: - Zone( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold=15, int p_max_pixel_threshold=0, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0, int p_overload_frames=0, int p_extend_alarm_frames=0 ) - { - Setup( p_monitor, p_id, p_label, p_type, p_polygon, p_alarm_rgb, p_check_method, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs, p_overload_frames, p_extend_alarm_frames ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold=15, int p_max_pixel_threshold=0, int p_min_alarm_pixels=50, int p_max_alarm_pixels=75000, const Coord &p_filter_box=Coord( 3, 3 ), int p_min_filter_pixels=50, int p_max_filter_pixels=50000, int p_min_blob_pixels=10, int p_max_blob_pixels=0, int p_min_blobs=0, int p_max_blobs=0, int p_overload_frames=0, int p_extend_alarm_frames=0) - { - Setup( p_monitor, p_id, p_label, Zone::ACTIVE, p_polygon, p_alarm_rgb, p_check_method, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs, p_overload_frames, p_extend_alarm_frames ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon ) - { - Setup( p_monitor, p_id, p_label, Zone::INACTIVE, p_polygon, RGB_BLACK, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); - } - Zone( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon ) - { - Setup( p_monitor, p_id, p_label, p_type, p_polygon, RGB_BLACK, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); - } + protected: + void Setup( + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold, + int p_max_pixel_threshold, + int p_min_alarm_pixels, + int p_max_alarm_pixels, + const Coord &p_filter_box, + int p_min_filter_pixels, + int p_max_filter_pixels, + int p_min_blob_pixels, + int p_max_blob_pixels, + int p_min_blobs, + int p_max_blobs, + int p_overload_frames, + int p_extend_alarm_frames); -public: - ~Zone(); + void std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum); - inline int Id() const { return( id ); } - inline const char *Label() const { return( label ); } - inline ZoneType Type() const { return( type ); } - inline bool IsActive() const { return( type == ACTIVE ); } - inline bool IsInclusive() const { return( type == INCLUSIVE ); } - inline bool IsExclusive() const { return( type == EXCLUSIVE ); } - inline bool IsPreclusive() const { return( type == PRECLUSIVE ); } - inline bool IsInactive() const { return( type == INACTIVE ); } - inline bool IsPrivacy() const { return( type == PRIVACY ); } - inline const Image *AlarmImage() const { return( image ); } - inline const Polygon &GetPolygon() const { return( polygon ); } - inline bool Alarmed() const { return( alarmed ); } - inline bool WasAlarmed() const { return( was_alarmed ); } - inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } - inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } - inline Coord GetAlarmCentre() const { return( alarm_centre ); } - inline unsigned int Score() const { return( score ); } + public: + Zone( + Monitor *p_monitor, + int p_id, + const char *p_label, + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold=15, + int p_max_pixel_threshold=0, + int p_min_alarm_pixels=50, + int p_max_alarm_pixels=75000, + const Coord &p_filter_box=Coord( 3, 3 ), + int p_min_filter_pixels=50, + int p_max_filter_pixels=50000, + int p_min_blob_pixels=10, + int p_max_blob_pixels=0, + int p_min_blobs=0, + int p_max_blobs=0, + int p_overload_frames=0, + int p_extend_alarm_frames=0) + : + monitor(p_monitor), + id(p_id), + label(p_label), + blob_stats{}, + stats(p_id) + { + Setup(p_type, p_polygon, p_alarm_rgb, p_check_method, p_min_pixel_threshold, p_max_pixel_threshold, p_min_alarm_pixels, p_max_alarm_pixels, p_filter_box, p_min_filter_pixels, p_max_filter_pixels, p_min_blob_pixels, p_max_blob_pixels, p_min_blobs, p_max_blobs, p_overload_frames, p_extend_alarm_frames ); + } - inline void ResetStats() - { - alarmed = false; - was_alarmed = false; - pixel_diff = 0; - alarm_pixels = 0; - alarm_filter_pixels = 0; - alarm_blob_pixels = 0; - alarm_blobs = 0; - min_blob_size = 0; - max_blob_size = 0; - score = 0; - } - void RecordStats( const Event *event ); - bool CheckAlarms( const Image *delta_image ); - bool DumpSettings( char *output, bool verbose ); + Zone(Monitor *p_monitor, int p_id, const char *p_label, const Polygon &p_polygon) + : + monitor(p_monitor), + id(p_id), + label(p_label), + blob_stats{}, + stats(p_id) + { + Setup(Zone::INACTIVE, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord(0, 0), 0, 0, 0, 0, 0, 0, 0, 0); + } + Zone(Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon) + : + monitor(p_monitor), + id(p_id), + label(p_label), + blob_stats{}, + stats(p_id) + { + Setup(p_type, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); + } - static bool ParsePolygonString( const char *polygon_string, Polygon &polygon ); - static bool ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ); - static int Load( Monitor *monitor, Zone **&zones ); - //================================================= - bool CheckOverloadCount(); - int GetOverloadCount(); - void SetOverloadCount(int nOverCount); - int GetOverloadFrames(); - //================================================= - bool CheckExtendAlarmCount(); - int GetExtendAlarmCount(); - void SetExtendAlarmCount(int nOverCount); - int GetExtendAlarmFrames(); - void SetScore(unsigned int nScore); - void SetAlarmImage(const Image* srcImage); + Zone(const Zone &z); + ~Zone(); - inline const Image *getPgImage() const { return( pg_image ); } - inline const Range *getRanges() const { return( ranges ); } + inline int Id() const { return id; } + inline const char *Label() const { return label.c_str(); } + inline ZoneType Type() const { return type; } + inline bool IsActive() const { return( type == ACTIVE ); } + inline bool IsInclusive() const { return( type == INCLUSIVE ); } + inline bool IsExclusive() const { return( type == EXCLUSIVE ); } + inline bool IsPreclusive() const { return( type == PRECLUSIVE ); } + inline bool IsInactive() const { return( type == INACTIVE ); } + inline bool IsPrivacy() const { return( type == PRIVACY ); } + inline const Image *AlarmImage() const { return image; } + inline const Polygon &GetPolygon() const { return polygon; } + inline bool Alarmed() const { return alarmed; } + inline bool WasAlarmed() const { return was_alarmed; } + inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } + inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } + inline Coord GetAlarmCentre() const { return stats.alarm_centre_; } + inline unsigned int Score() const { return stats.score_; } + + inline void ResetStats() { + alarmed = false; + was_alarmed = false; + stats.Reset(); + } + void RecordStats( const Event *event ); + ZoneStats const &GetStats() const { + stats.DumpToLog("GetStats"); + return stats; + }; + + bool CheckAlarms(const Image *delta_image); + bool DumpSettings(char *output, bool verbose) const; + + static bool ParsePolygonString( const char *polygon_string, Polygon &polygon ); + static bool ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ); + static std::vector Load(Monitor *monitor); + //================================================= + bool CheckOverloadCount(); + int GetOverloadCount(); + void SetOverloadCount(int nOverCount); + int GetOverloadFrames(); + //================================================= + bool CheckExtendAlarmCount(); + int GetExtendAlarmCount(); + void SetExtendAlarmCount(int nOverCount); + int GetExtendAlarmFrames(); + void SetScore(unsigned int nScore); + void SetAlarmImage(const Image* srcImage); + + inline const Image *getPgImage() const { return pg_image; } + inline const Range *getRanges() const { return ranges; } }; #endif // ZM_ZONE_H diff --git a/src/zm_zone_stats.h b/src/zm_zone_stats.h new file mode 100644 index 000000000..f963a0eb4 --- /dev/null +++ b/src/zm_zone_stats.h @@ -0,0 +1,94 @@ +// +// ZoneMinder Zone Stats Class Interfaces, $Date$, $Revision$ +// Copyright (C) 2021 Isaac Connor +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#ifndef ZM_ZONE_STATS_H +#define ZM_ZONE_STATS_H + +#include "zm_box.h" +#include "zm_coord.h" +#include "zm_logger.h" + +class ZoneStats { + public: + explicit ZoneStats(int zone_id) : + zone_id_(zone_id), + pixel_diff_(0), + alarm_pixels_(0), + alarm_filter_pixels_(0), + alarm_blob_pixels_(0), + alarm_blobs_(0), + min_blob_size_(0), + max_blob_size_(0), + alarm_box_({}), + alarm_centre_({}), + score_(0) {}; + + void Reset() { + pixel_diff_ = 0; + alarm_pixels_ = 0; + alarm_filter_pixels_ = 0; + alarm_blob_pixels_ = 0; + alarm_blobs_ = 0; + min_blob_size_ = 0; + max_blob_size_ = 0; + alarm_box_.LoX(0); + alarm_box_.LoY(0); + alarm_box_.HiX(0); + alarm_box_.HiY(0); + alarm_centre_ = {}; + score_ = 0; + } + + void DumpToLog(const char *prefix) const { + Debug(1, + "ZoneStat: %s zone_id: %d pixel_diff=%d alarm_pixels=%d alarm_filter_pixels=%d alarm_blob_pixels=%d alarm_blobs=%d min_blob_size=%d max_blob_size=%d alarm_box=(%d,%d=>%d,%d) alarm_center=(%d,%d) score=%d", + prefix, + zone_id_, + pixel_diff_, + alarm_pixels_, + alarm_filter_pixels_, + alarm_blob_pixels_, + alarm_blobs_, + min_blob_size_, + max_blob_size_, + alarm_box_.LoX(), + alarm_box_.LoY(), + alarm_box_.HiX(), + alarm_box_.HiY(), + alarm_centre_.X(), + alarm_centre_.Y(), + score_ + ); + } + + public: + int zone_id_; + int pixel_diff_; + unsigned int alarm_pixels_; + int alarm_filter_pixels_; + int alarm_blob_pixels_; + int alarm_blobs_; + int min_blob_size_; + int max_blob_size_; + Box alarm_box_; + Coord alarm_centre_; + unsigned int score_; +}; + +#endif // ZM_ZONE_STATS_H diff --git a/src/zma.cpp b/src/zma.cpp deleted file mode 100644 index edaf9c722..000000000 --- a/src/zma.cpp +++ /dev/null @@ -1,187 +0,0 @@ -// -// ZoneMinder Analysis Daemon, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// - -/* - -=head1 NAME - -zma - The ZoneMinder Analysis daemon - -=head1 SYNOPSIS - - zma -m - zma --monitor - zma -h - zma --help - zma -v - zma --version - -=head1 DESCRIPTION - -This is the component that goes through the captured frames and checks them -for motion which might generate an alarm or event. It generally keeps up with -the Capture daemon but if very busy may skip some frames to prevent it falling -behind. - -=head1 OPTIONS - - -m, --monitor_id - ID of the monitor to analyse - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_fifo.h" - -void Usage() { - fprintf(stderr, "zma -m \n"); - fprintf(stderr, "Options:\n"); - fprintf(stderr, " -m, --monitor : Specify which monitor to use\n"); - fprintf(stderr, " -h, --help : This screen\n"); - fprintf(stderr, " -v, --version : Report the installed version of ZoneMinder\n"); - exit(0); -} - -int main( int argc, char *argv[] ) { - self = argv[0]; - - srand(getpid() * time(nullptr)); - - int id = -1; - - static struct option long_options[] = { - {"monitor", 1, nullptr, 'm'}, - {"help", 0, nullptr, 'h'}, - {"version", 0, nullptr, 'v'}, - {nullptr, 0, nullptr, 0} - }; - - while (1) { - int option_index = 0; - - int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index); - if ( c == -1 ) { - break; - } - - switch (c) { - case 'm': - id = atoi(optarg); - break; - case 'h': - case '?': - Usage(); - break; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - default: - //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); - break; - } - } - - if (optind < argc) { - fprintf(stderr, "Extraneous options, "); - while (optind < argc) - printf("%s ", argv[optind++]); - printf("\n"); - Usage(); - } - - if ( id < 0 ) { - fprintf(stderr, "Bogus monitor %d\n", id); - Usage(); - exit(0); - } - - char log_id_string[16]; - snprintf(log_id_string, sizeof(log_id_string), "zma_m%d", id); - - zmLoadConfig(); - - logInit(log_id_string); - - hwcaps_detect(); - - Monitor *monitor = Monitor::Load(id, true, Monitor::ANALYSIS); - zmFifoDbgInit( monitor ); - - if ( monitor ) { - Info("In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled()); - - zmSetDefaultHupHandler(); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); - - sigset_t block_set; - sigemptyset(&block_set); - - useconds_t analysis_rate = monitor->GetAnalysisRate(); - unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay(); - time_t last_analysis_update_time, cur_time; - monitor->UpdateAdaptiveSkip(); - last_analysis_update_time = time(nullptr); - - while( (!zm_terminate) && monitor->ShmValid() ) { - // Process the next image - sigprocmask(SIG_BLOCK, &block_set, nullptr); - - // Some periodic updates are required for variable capturing framerate - if ( analysis_update_delay ) { - cur_time = time(nullptr); - if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) { - analysis_rate = monitor->GetAnalysisRate(); - monitor->UpdateAdaptiveSkip(); - last_analysis_update_time = cur_time; - } - } - - if ( !monitor->Analyse() ) { - usleep(monitor->Active()?ZM_SAMPLE_RATE:ZM_SUSPENDED_RATE); - } else if ( analysis_rate ) { - usleep(analysis_rate); - } - - if ( zm_reload ) { - monitor->Reload(); - logTerm(); - logInit(log_id_string); - zm_reload = false; - } - sigprocmask(SIG_UNBLOCK, &block_set, nullptr); - } // end while ! zm_terminate - delete monitor; - } else { - fprintf(stderr, "Can't find monitor with id of %d\n", id); - } - Image::Deinitialise(); - logTerm(); - zmDbClose(); - return 0; -} diff --git a/src/zmc.cpp b/src/zmc.cpp index 8376c91ba..c2ee0a071 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -53,35 +53,31 @@ possible, this should run at more or less constant speed. */ -#include -#include -#if defined(__FreeBSD__) -#include -#else -#include -#endif - -#if !defined(MAXINT) -#define MAXINT INT_MAX -#endif - #include "zm.h" +#include "zm_camera.h" #include "zm_db.h" -#include "zm_time.h" -#include "zm_signal.h" +#include "zm_define.h" +#include "zm_fifo.h" #include "zm_monitor.h" +#include "zm_rtsp_server_thread.h" +#include "zm_signal.h" +#include "zm_time.h" +#include "zm_utils.h" +#include +#include +#include void Usage() { fprintf(stderr, "zmc -d or -r -H -P -p or -f or -m \n"); fprintf(stderr, "Options:\n"); #if defined(BSD) - fprintf(stderr, " -d, --device : For local cameras, device to access. E.g /dev/bktr0 etc\n"); + fprintf(stderr, " -d, --device : For local cameras, device to access. E.g /dev/bktr0 etc\n"); #else - fprintf(stderr, " -d, --device : For local cameras, device to access. E.g /dev/video0 etc\n"); + fprintf(stderr, " -d, --device : For local cameras, device to access. E.g /dev/video0 etc\n"); #endif - fprintf(stderr, " -f, --file : For local images, jpg file to access.\n"); - fprintf(stderr, " -m, --monitor : For sources associated with a single monitor\n"); + fprintf(stderr, " -f, --file : For local images, jpg file to access.\n"); + fprintf(stderr, " -m, --monitor : For sources associated with a single monitor\n"); fprintf(stderr, " -h, --help : This screen\n"); fprintf(stderr, " -v, --version : Report the installed version of ZoneMinder\n"); exit(0); @@ -188,36 +184,37 @@ int main(int argc, char *argv[]) { } logInit(log_id_string); - zmLoadConfig(); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); logInit(log_id_string); - hwcaps_detect(); + HwCapsDetect(); - Monitor **monitors = nullptr; - int n_monitors = 0; + std::vector> monitors; #if ZM_HAS_V4L if ( device[0] ) { - n_monitors = Monitor::LoadLocalMonitors(device, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE); } else #endif // ZM_HAS_V4L if ( host[0] ) { if ( !port ) port = "80"; - n_monitors = Monitor::LoadRemoteMonitors(protocol, host, port, path, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadRemoteMonitors(protocol, host, port, path, Monitor::CAPTURE); } else if ( file[0] ) { - n_monitors = Monitor::LoadFileMonitors(file, monitors, Monitor::CAPTURE); + monitors = Monitor::LoadFileMonitors(file, Monitor::CAPTURE); } else { - Monitor *monitor = Monitor::Load(monitor_id, true, Monitor::CAPTURE); + std::shared_ptr monitor = Monitor::Load(monitor_id, true, Monitor::CAPTURE); if ( monitor ) { - monitors = new Monitor *[1]; - monitors[0] = monitor; - n_monitors = 1; + monitors.push_back(monitor); } } - if ( !n_monitors ) { + if (monitors.empty()) { Error("No monitors found"); exit(-1); + } else { + Debug(2, "%zu monitors loaded", monitors.size()); } Info("Starting Capture version %s", ZM_VERSION); @@ -233,148 +230,159 @@ int main(int argc, char *argv[]) { sigaddset(&block_set, SIGUSR2); int result = 0; - int prime_capture_log_count = 0; - while ( !zm_terminate ) { + while (!zm_terminate) { result = 0; static char sql[ZM_SQL_SML_BUFSIZ]; - for ( int i = 0; i < n_monitors; i++ ) { + + for (const std::shared_ptr &monitor : monitors) { + monitor->LoadCamera(); + + if (!monitor->connect()) { + Warning("Couldn't connect to monitor %d", monitor->Id()); + } time_t now = (time_t)time(nullptr); - monitors[i]->setStartupTime(now); + monitor->setStartupTime(now); + monitor->setHeartbeatTime(now); + + snprintf(sql, sizeof(sql), + "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)" + " VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", + monitor->Id()); + zmDbDo(sql); + + int sleep_time = 0; + while (monitor->PrimeCapture() <= 0) { + if (prime_capture_log_count % 60) { + logPrintf(Logger::ERROR+monitor->Importance(), + "Failed to prime capture of initial monitor"); + } else { + Debug(1, "Failed to prime capture of initial monitor"); + } + prime_capture_log_count ++; + if (zm_terminate) break; + if (sleep_time < 60) sleep_time++; + sleep(sleep_time); + } + if (zm_terminate) break; snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','Running')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'", + monitor->Id()); + zmDbDo(sql); } // end foreach monitor + if (zm_terminate) break; - // Outer primary loop, handles connection to camera - if ( monitors[0]->PrimeCapture() < 0 ) { - if ( prime_capture_log_count % 60 ) { - Error("Failed to prime capture of initial monitor"); - } else { - Debug(1, "Failed to prime capture of initial monitor"); - } - prime_capture_log_count ++; - if ( !zm_terminate ) - sleep(10); - continue; - } + int *capture_delays = new int[monitors.size()]; + int *alarm_capture_delays = new int[monitors.size()]; + struct timeval * last_capture_times = new struct timeval[monitors.size()]; - int *capture_delays = new int[n_monitors]; - int *alarm_capture_delays = new int[n_monitors]; - int *next_delays = new int[n_monitors]; - struct timeval * last_capture_times = new struct timeval[n_monitors]; - for ( int i = 0; i < n_monitors; i++ ) { + for (size_t i = 0; i < monitors.size(); i++) { last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0; capture_delays[i] = monitors[i]->GetCaptureDelay(); alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); - snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','Connected')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - } // end foreach monitor + Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)", + capture_delays[i], alarm_capture_delays[i]); + } struct timeval now; struct DeltaTimeval delta_time; - while ( !zm_terminate ) { + int sleep_time = 0; + + while (!zm_terminate) { //sigprocmask(SIG_BLOCK, &block_set, 0); - for ( int i = 0; i < n_monitors; i++ ) { - long min_delay = MAXINT; + for (size_t i = 0; i < monitors.size(); i++) { + monitors[i]->CheckAction(); - gettimeofday(&now, nullptr); - for ( int j = 0; j < n_monitors; j++ ) { - if ( last_capture_times[j].tv_sec ) { - DELTA_TIMEVAL(delta_time, now, last_capture_times[j], DT_PREC_3); - if ( monitors[i]->GetState() == Monitor::ALARM ) - next_delays[j] = alarm_capture_delays[j]-delta_time.delta; - else - next_delays[j] = capture_delays[j]-delta_time.delta; - if ( next_delays[j] < 0 ) - next_delays[j] = 0; - } else { - next_delays[j] = 0; - } - if ( next_delays[j] <= min_delay ) { - min_delay = next_delays[j]; - } - } // end foreach monitor - - if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { - if ( monitors[i]->PreCapture() < 0 ) { - Error("Failed to pre-capture monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - if ( monitors[i]->Capture() < 0 ) { - Info("Failed to capture image from monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - if ( monitors[i]->PostCapture() < 0 ) { - Error("Failed to post-capture monitor %d %s (%d/%d)", - monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); - monitors[i]->Close(); - result = -1; - break; - } - - if ( next_delays[i] > 0 ) { - gettimeofday(&now, nullptr); - DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_3); - long sleep_time = next_delays[i]-delta_time.delta; - if ( sleep_time > 0 ) { - usleep(sleep_time*(DT_MAXGRAN/DT_PREC_3)); - } - } - gettimeofday(&(last_capture_times[i]), nullptr); - } // end if next_delay <= min_delay || next_delays[i] <= 0 ) - - } // end foreach n_monitors - //sigprocmask(SIG_UNBLOCK, &block_set, 0); - if ( zm_reload ) { - for ( int i = 0; i < n_monitors; i++ ) { - monitors[i]->Reload(); + if (monitors[i]->PreCapture() < 0) { + Error("Failed to pre-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; } - logTerm(); - logInit(log_id_string); - zm_reload = false; - } - if ( result < 0 ) { + if (monitors[i]->Capture() < 0) { + Error("Failed to capture image from monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; + } + if (monitors[i]->PostCapture() < 0) { + Error("Failed to post-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); + result = -1; + break; + } + + // capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate. + int delay = (monitors[i]->GetState() == Monitor::ALARM) ? alarm_capture_delays[i] : capture_delays[i]; + if (delay) { + gettimeofday(&now, nullptr); + if (last_capture_times[i].tv_sec) { + // DT_PREC_3 means that the value will be in thousands of a second + DELTA_TIMEVAL(delta_time, now, last_capture_times[i], DT_PREC_6); + + // You have to add back in the previous sleep time + sleep_time = delay - (delta_time.delta - sleep_time); + Debug(4, + "Sleep time is %d from now: %" PRIi64 ".%" PRIi64" last: %" PRIi64 ".% " PRIi64 " delta %lu delay: %d", + sleep_time, + static_cast(now.tv_sec), + static_cast(now.tv_usec), + static_cast(last_capture_times[i].tv_sec), + static_cast(last_capture_times[i].tv_usec), + delta_time.delta, + delay); + + if (sleep_time > 0) { + Debug(4, "usleeping (%d)", sleep_time); + usleep(sleep_time); + } + } // end if has a last_capture time + last_capture_times[i] = now; + } // end if delay + } // end foreach n_monitors + + if ((result < 0) or zm_reload) { // Failure, try reconnecting - sleep(5); break; } - } // end while ! zm_terminate + } // end while ! zm_terminate and connected + + for (size_t i = 0; i < monitors.size(); i++) { + monitors[i]->Close(); + monitors[i]->disconnect(); + } + delete [] alarm_capture_delays; delete [] capture_delays; - delete [] next_delays; delete [] last_capture_times; - } // end while ! zm_terminate outer connection loop - for ( int i = 0; i < n_monitors; i++ ) { + if (zm_reload) { + for (std::shared_ptr &monitor : monitors) { + monitor->Reload(); + } + logTerm(); + logInit(log_id_string); + + zm_reload = false; + } // end if zm_reload + } // end while ! zm_terminate outer connection loop + + Debug(1,"Updating Monitor status"); + + for (std::shared_ptr &monitor : monitors) { static char sql[ZM_SQL_SML_BUFSIZ]; snprintf(sql, sizeof(sql), - "REPLACE INTO Monitor_Status (MonitorId, Status) VALUES ('%d','NotRunning')", - monitors[i]->Id()); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - } - delete monitors[i]; + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", + monitor->Id()); + zmDbDo(sql); } - delete [] monitors; Image::Deinitialise(); + Debug(1, "terminating"); logTerm(); + dbQueue.stop(); zmDbClose(); return zm_terminate ? 0 : result; diff --git a/src/zms.cpp b/src/zms.cpp index 9b661c564..d98f24418 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -17,19 +17,15 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -#include -#include -#include -#include - #include "zm.h" #include "zm_db.h" #include "zm_user.h" #include "zm_signal.h" -#include "zm_monitor.h" #include "zm_monitorstream.h" #include "zm_eventstream.h" -#include "zm_fifo.h" +#include "zm_fifo_stream.h" +#include +#include bool ValidateAccess(User *user, int mon_id) { bool allowed = true; @@ -86,100 +82,107 @@ int main(int argc, const char *argv[], char **envp) { nph = true; } - zmLoadConfig(); char log_id_string[32] = "zms"; logInit(log_id_string); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); + logInit(log_id_string); + for (char **env = envp; *env != 0; env++) { char *thisEnv = *env; Debug(1, "env: %s", thisEnv); } const char *query = getenv("QUERY_STRING"); - if ( query ) { - Debug(1, "Query: %s", query); - - char temp_query[1024]; - strncpy(temp_query, query, sizeof(temp_query)-1); - char *q_ptr = temp_query; - char *parms[16]; // Shouldn't be more than this - int parm_no = 0; - while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { - parm_no++; - q_ptr = nullptr; - } - - for ( int p = 0; p < parm_no; p++ ) { - char *name = strtok(parms[p], "="); - char const *value = strtok(nullptr, "="); - if ( !value ) - value = ""; - if ( !strcmp(name, "source") ) { - source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; - if ( !strcmp(value, "fifo") ) - source = ZMS_FIFO; - } else if ( !strcmp(name, "mode") ) { - mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; - mode = !strcmp(value, "raw")?ZMS_RAW:mode; - mode = !strcmp(value, "zip")?ZMS_ZIP:mode; - mode = !strcmp(value, "single")?ZMS_SINGLE:mode; - } else if ( !strcmp(name, "format") ) { - strncpy( format, value, sizeof(format) ); - } else if ( !strcmp(name, "monitor") ) { - monitor_id = atoi(value); - if ( source == ZMS_UNKNOWN ) - source = ZMS_MONITOR; - } else if ( !strcmp(name, "time") ) { - event_time = atoi(value); - } else if ( !strcmp(name, "event") ) { - event_id = strtoull(value, nullptr, 10); - source = ZMS_EVENT; - } else if ( !strcmp(name, "frame") ) { - frame_id = strtoull(value, nullptr, 10); - source = ZMS_EVENT; - } else if ( !strcmp(name, "scale") ) { - scale = atoi(value); - } else if ( !strcmp(name, "rate") ) { - rate = atoi(value); - } else if ( !strcmp(name, "maxfps") ) { - maxfps = atof(value); - } else if ( !strcmp(name, "bitrate") ) { - bitrate = atoi(value); - } else if ( !strcmp(name, "ttl") ) { - ttl = atoi(value); - } else if ( !strcmp(name, "replay") ) { - if ( !strcmp(value, "gapless") ) { - replay = EventStream::MODE_ALL_GAPLESS; - } else if ( !strcmp(value, "all") ) { - replay = EventStream::MODE_ALL; - } else if ( !strcmp(value, "none") ) { - replay = EventStream::MODE_NONE; - } else if ( !strcmp(value, "single") ) { - replay = EventStream::MODE_SINGLE; - } else { - Error("Unsupported value %s for replay, defaulting to none", value); - } - } else if ( !strcmp(name, "connkey") ) { - connkey = atoi(value); - } else if ( !strcmp(name, "buffer") ) { - playback_buffer = atoi(value); - } else if ( !strcmp(name, "auth") ) { - strncpy(auth, value, sizeof(auth)-1); - } else if ( !strcmp(name, "token") ) { - jwt_token_str = value; - Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); - } else if ( !strcmp(name, "user") ) { - username = UriDecode(value); - } else if ( !strcmp(name, "pass") ) { - password = UriDecode(value); - Debug(1, "Have %s for password", password.c_str()); - } else { - Debug(1, "Unknown parameter passed to zms %s=%s", name, value); - } // end if possible parameter names - } // end foreach parm - } else { + if ( query == nullptr ) { Fatal("No query string."); + return 0; } // end if query + Debug(1, "Query: %s", query); + + char *q_ptr = (char *)query; + char *parms[16]; // Shouldn't be more than this + int parm_no = 0; + while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { + parm_no++; + q_ptr = nullptr; + } + + for ( int p = 0; p < parm_no; p++ ) { + char *name = strtok(parms[p], "="); + char const *value = strtok(nullptr, "="); + if ( !value ) + value = ""; + if ( !strcmp(name, "source") ) { + if ( !strcmp(value, "event") ) { + source = ZMS_EVENT; + } else if ( !strcmp(value, "fifo") ) { + source = ZMS_FIFO; + } else { + source = ZMS_MONITOR; + } + } else if ( !strcmp(name, "mode") ) { + mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG; + mode = !strcmp(value, "raw")?ZMS_RAW:mode; + mode = !strcmp(value, "zip")?ZMS_ZIP:mode; + mode = !strcmp(value, "single")?ZMS_SINGLE:mode; + } else if ( !strcmp(name, "format") ) { + strncpy(format, value, sizeof(format)-1); + } else if ( !strcmp(name, "monitor") ) { + monitor_id = atoi(value); + if ( source == ZMS_UNKNOWN ) + source = ZMS_MONITOR; + } else if ( !strcmp(name, "time") ) { + event_time = atoi(value); + } else if ( !strcmp(name, "event") ) { + event_id = strtoull(value, nullptr, 10); + source = ZMS_EVENT; + } else if ( !strcmp(name, "frame") ) { + frame_id = strtoull(value, nullptr, 10); + source = ZMS_EVENT; + } else if ( !strcmp(name, "scale") ) { + scale = atoi(value); + } else if ( !strcmp(name, "rate") ) { + rate = atoi(value); + } else if ( !strcmp(name, "maxfps") ) { + maxfps = atof(value); + } else if ( !strcmp(name, "bitrate") ) { + bitrate = atoi(value); + } else if ( !strcmp(name, "ttl") ) { + ttl = atoi(value); + } else if ( !strcmp(name, "replay") ) { + if ( !strcmp(value, "gapless") ) { + replay = EventStream::MODE_ALL_GAPLESS; + } else if ( !strcmp(value, "all") ) { + replay = EventStream::MODE_ALL; + } else if ( !strcmp(value, "none") ) { + replay = EventStream::MODE_NONE; + } else if ( !strcmp(value, "single") ) { + replay = EventStream::MODE_SINGLE; + } else { + Error("Unsupported value %s for replay, defaulting to none", value); + } + } else if ( !strcmp(name, "connkey") ) { + connkey = atoi(value); + } else if ( !strcmp(name, "buffer") ) { + playback_buffer = atoi(value); + } else if ( !strcmp(name, "auth") ) { + strncpy(auth, value, sizeof(auth)-1); + } else if ( !strcmp(name, "token") ) { + jwt_token_str = value; + Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); + } else if ( !strcmp(name, "user") ) { + username = UriDecode(value); + } else if ( !strcmp(name, "pass") ) { + password = UriDecode(value); + Debug(1, "Have %s for password", password.c_str()); + } else { + Debug(1, "Unknown parameter passed to zms %s=%s", name, value); + } // end if possible parameter names + } // end foreach parm + if ( monitor_id ) { snprintf(log_id_string, sizeof(log_id_string), "zms_m%d", monitor_id); } else { @@ -227,7 +230,8 @@ int main(int argc, const char *argv[], char **envp) { user = nullptr; } // end if config.opt_use_auth - hwcaps_detect(); + HwCapsDetect(); + Image::Initialise(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -239,8 +243,9 @@ int main(int argc, const char *argv[], char **envp) { time_t now = time(nullptr); char date_string[64]; + tm now_tm = {}; strftime(date_string, sizeof(date_string)-1, - "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&now, &now_tm)); fputs("Last-Modified: ", stdout); fputs(date_string, stdout); @@ -259,7 +264,13 @@ int main(int argc, const char *argv[], char **envp) { stream.setStreamTTL(ttl); stream.setStreamQueue(connkey); stream.setStreamBuffer(playback_buffer); - stream.setStreamStart(monitor_id); + if ( !stream.setStreamStart(monitor_id) ) { + Error("Unable set start stream for monitor %d", monitor_id); + stream.sendTextFrame("Unable to connect to monitor"); + logTerm(); + zmDbClose(); + return -1; + } if ( mode == ZMS_JPEG ) { stream.setStreamType(MonitorStream::STREAM_JPEG); @@ -328,7 +339,10 @@ int main(int argc, const char *argv[], char **envp) { Error("Neither a monitor or event was specified."); } // end if monitor or event + Debug(1, "Terminating"); + Image::Deinitialise(); logTerm(); + dbQueue.stop(); zmDbClose(); return 0; diff --git a/src/zmu.cpp b/src/zmu.cpp index bdc467812..acc4b21dc 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -86,15 +86,14 @@ Options for use with monitors: */ -#include -#include - #include "zm.h" #include "zm_db.h" #include "zm_user.h" #include "zm_signal.h" #include "zm_monitor.h" #include "zm_local_camera.h" +#include +#include void Usage(int status=-1) { fputs( @@ -184,7 +183,7 @@ bool ValidateAccess(User *user, int mon_id, int function) { if ( user->getMonitors() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { + if ( function & (ZMU_NOALARM|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { if ( user->getMonitors() < User::PERM_EDIT ) allowed = false; } @@ -275,7 +274,7 @@ int main(int argc, char *argv[]) { while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:T:", long_options, &option_index); + int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); if ( c == -1 ) { break; } @@ -419,8 +418,10 @@ int main(int argc, char *argv[]) { } //printf( "Monitor %d, Function %d\n", mon_id, function ); - zmLoadConfig(); - + logInit("zmu"); + zmLoadStaticConfig(); + zmDbConnect(); + zmLoadDBConfig(); logInit("zmu"); zmSetDefaultTermHandler(); @@ -475,19 +476,17 @@ int main(int argc, char *argv[]) { } // end if auth if ( mon_id > 0 ) { - Monitor *monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); + std::shared_ptr monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); if ( !monitor ) { Error("Unable to load monitor %d", mon_id); exit_zmu(-1); } // end if ! MONITOR if ( verbose ) { - printf("Monitor %d(%s)\n", monitor->Id(), monitor->Name()); + printf("Monitor %u(%s)\n", monitor->Id(), monitor->Name()); } if ( !monitor->connect() ) { Error("Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name()); - delete monitor; - monitor = nullptr; exit_zmu(-1); } @@ -498,7 +497,6 @@ int main(int argc, char *argv[]) { if ( verbose ) { printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle")); } else { - if ( have_output ) fputc(separator, stdout); printf("%d", state); have_output = true; } @@ -507,8 +505,10 @@ int main(int argc, char *argv[]) { struct timeval timestamp = monitor->GetTimestamp(image_idx); if ( verbose ) { char timestamp_str[64] = "None"; - if ( timestamp.tv_sec ) - strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime(×tamp.tv_sec)); + if ( timestamp.tv_sec ) { + tm tm_info = {}; + strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(×tamp.tv_sec, &tm_info)); + } if ( image_idx == -1 ) printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000); else @@ -521,19 +521,19 @@ int main(int argc, char *argv[]) { } if ( function & ZMU_READ_IDX ) { if ( verbose ) - printf("Last read index: %d\n", monitor->GetLastReadIndex()); + printf("Last read index: %u\n", monitor->GetLastReadIndex()); else { if ( have_output ) fputc(separator, stdout); - printf("%d", monitor->GetLastReadIndex()); + printf("%u", monitor->GetLastReadIndex()); have_output = true; } } if ( function & ZMU_WRITE_IDX ) { if ( verbose ) { - printf("Last write index: %d\n", monitor->GetLastWriteIndex()); + printf("Last write index: %u\n", monitor->GetLastWriteIndex()); } else { if ( have_output ) fputc(separator, stdout); - printf("%d", monitor->GetLastWriteIndex()); + printf("%u", monitor->GetLastWriteIndex()); have_output = true; } } @@ -548,19 +548,20 @@ int main(int argc, char *argv[]) { } if ( function & ZMU_FPS ) { if ( verbose ) { - printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); + printf("Current capture rate: %.2f frames per second, analysis rate: %.2f frames per second\n", + monitor->get_capture_fps(), monitor->get_analysis_fps()); } else { if ( have_output ) fputc(separator, stdout); - printf("%.2f", monitor->GetFPS()); + printf("capture: %.2f, analysis: %.2f", monitor->get_capture_fps(), monitor->get_analysis_fps()); have_output = true; } } if ( function & ZMU_IMAGE ) { if ( verbose ) { if ( image_idx == -1 ) - printf("Dumping last image captured to Monitor%d.jpg", monitor->Id()); + printf("Dumping last image captured to Monitor%u.jpg", monitor->Id()); else - printf("Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id()); + printf("Dumping buffer image %d to Monitor%u.jpg", image_idx, monitor->Id()); if ( scale != -1 ) printf(", scaling by %d%%", scale); printf("\n"); @@ -569,7 +570,7 @@ int main(int argc, char *argv[]) { } if ( function & ZMU_ZONES ) { if ( verbose ) - printf("Dumping zone image to Zones%d.jpg\n", monitor->Id()); + printf("Dumping zone image to Zones%u.jpg\n", monitor->Id()); monitor->DumpZoneImage(zoneString); } if ( function & ZMU_ALARM ) { @@ -584,12 +585,21 @@ int main(int argc, char *argv[]) { monitor->GetLastEventId() ); } + + // Ensure that we are not recording. So the forced alarm is distinct from what was recording before + monitor->ForceAlarmOff(); monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); - while ( ((state = monitor->GetState()) != Monitor::ALARM) && !zm_terminate ) { + int wait = 10*1000*1000; // 10 seconds + while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait) { // Wait for monitor to notice. usleep(1000); + wait -= 1000; + } + if ( monitor->GetState() != Monitor::ALARM and !wait ) { + Error("Monitor failed to respond to forced alarm."); + } else { + printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); } - printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); } } // end if ZMU_ALARM @@ -700,8 +710,6 @@ int main(int argc, char *argv[]) { if ( !function ) { Usage(); } - delete monitor; - monitor = nullptr; } else { // non monitor functions if ( function & ZMU_QUERY ) { #if ZM_HAS_V4L @@ -717,35 +725,29 @@ int main(int argc, char *argv[]) { if ( function & ZMU_LIST ) { std::string sql = "SELECT `Id`, `Function`+0 FROM `Monitors`"; - if ( !verbose ) { + if (!verbose) { sql += "WHERE `Function` != 'None'"; } sql += " ORDER BY Id ASC"; - if ( mysql_query(&dbconn, sql.c_str()) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit_zmu(mysql_errno(&dbconn)); + MYSQL_RES *result = zmDbFetch(sql.c_str()); + if (!result) { + exit_zmu(-1); } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit_zmu(mysql_errno(&dbconn)); - } - Debug(1, "Got %d monitors", mysql_num_rows(result)); + Debug(1, "Got %" PRIu64 " monitors", static_cast(mysql_num_rows(result))); printf("%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate"); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { - int mon_id = atoi(dbrow[0]); - int function = atoi(dbrow[1]); - if ( !user || user->canAccess(mon_id) ) { - if ( function > 1 ) { - Monitor *monitor = Monitor::Load(mon_id, false, Monitor::QUERY); + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + int monitor_id = atoi(dbrow[0]); + int monitor_function = atoi(dbrow[1]); + if ( !user || user->canAccess(monitor_id) ) { + if ( monitor_function > 1 ) { + std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); if ( monitor && monitor->connect() ) { struct timeval tv = monitor->GetTimestamp(); printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8" PRIu64 "%8.2f\n", monitor->Id(), - function, + monitor_function, monitor->GetState(), monitor->GetTriggerState(), tv.tv_sec, tv.tv_usec/10000, @@ -754,7 +756,6 @@ int main(int argc, char *argv[]) { monitor->GetLastEventId(), monitor->GetFPS() ); - delete monitor; } } else { struct timeval tv = { 0, 0 }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..e0c68aeff --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,40 @@ +# This file is part of the ZoneMinder Project. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + +include(Catch) + +set(TEST_SOURCES + zm_comms.cpp + zm_crypt.cpp + zm_font.cpp + zm_utils.cpp) + +add_executable(tests main.cpp ${TEST_SOURCES}) + +target_link_libraries(tests + PRIVATE + zm-core-interface + zm + ${ZM_BIN_LIBS} + bcrypt + Catch2::Catch2) + +target_include_directories(tests + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}) + +catch_discover_tests(tests) + +add_custom_command(TARGET tests + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${CMAKE_CURRENT_BINARY_DIR}/data/ + BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/data/) diff --git a/tests/data/fonts/01_bad_magic.zmfnt b/tests/data/fonts/01_bad_magic.zmfnt new file mode 100644 index 000000000..2cfaf0818 Binary files /dev/null and b/tests/data/fonts/01_bad_magic.zmfnt differ diff --git a/tests/data/fonts/02_variant_invalid.zmfnt b/tests/data/fonts/02_variant_invalid.zmfnt new file mode 100644 index 000000000..9b53e85ab Binary files /dev/null and b/tests/data/fonts/02_variant_invalid.zmfnt differ diff --git a/tests/data/fonts/03_missing_cps.zmfnt b/tests/data/fonts/03_missing_cps.zmfnt new file mode 100644 index 000000000..71c58970d Binary files /dev/null and b/tests/data/fonts/03_missing_cps.zmfnt differ diff --git a/tests/data/fonts/04_valid.zmfnt b/tests/data/fonts/04_valid.zmfnt new file mode 100644 index 000000000..0a4ad6653 Binary files /dev/null and b/tests/data/fonts/04_valid.zmfnt differ diff --git a/tests/data/fonts/generate_fonts.py b/tests/data/fonts/generate_fonts.py new file mode 100644 index 000000000..275c2021a --- /dev/null +++ b/tests/data/fonts/generate_fonts.py @@ -0,0 +1,57 @@ +#!/bin/python + +import struct + +GOOD_MAGIC = b"ZMFNT\0" +BAD_MAGIC = b"ABCDE\0" +NUM_FONT_SIZES = 4 + +ZMFNT_VERSION = 1 + + +class FontFile: + def __init__(self, path): + self.path = path + + def write_file_header(self, magic): + with open(self.path, "wb") as f: + f.write(magic) + f.write(struct.pack("B", ZMFNT_VERSION)) + f.write(struct.pack("B", 0)) # pad + + def write_bm_header(self, height, width, cp_count, idx, padding): + with open(self.path, "ab") as f: + f.write(struct.pack("HHIIBBBB", height, width, cp_count, idx, padding, 0, 0, 0)) + + def write_codepoints(self, value, height, count): + with open(self.path, "ab") as f: + for _ in range(height * count): + f.write(struct.pack("Q", value)) + + +font = FontFile("01_bad_magic.zmfnt") +font.write_file_header(BAD_MAGIC) + +# height, width and number of codepoints out of bounds +font = FontFile("02_variant_invalid.zmfnt") +font.write_file_header(GOOD_MAGIC) +font.write_bm_header(201, 65, 256, 0, 2) + +# mismatch between number of codepoints specified in header and actually stored ones +font = FontFile("03_missing_cps.zmfnt") +font.write_file_header(GOOD_MAGIC) +offs = 0 +for _ in range(NUM_FONT_SIZES): + font.write_bm_header(10, 10, 10, offs, 2) + offs += 10 * 10 +for _ in range(NUM_FONT_SIZES): + font.write_codepoints(1, 10, 9) + +font = FontFile("04_valid.zmfnt") +font.write_file_header(GOOD_MAGIC) +offs = 0 +for i in range(NUM_FONT_SIZES): + font.write_bm_header(10 + i, 10 + i, 10, offs, 2) + offs += 10 * (10 + i) +for i in range(NUM_FONT_SIZES): + font.write_codepoints(i, 10 + i, 10) diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 000000000..1a00d1291 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,19 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" diff --git a/tests/zm_comms.cpp b/tests/zm_comms.cpp new file mode 100644 index 000000000..dfe98e1bf --- /dev/null +++ b/tests/zm_comms.cpp @@ -0,0 +1,316 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "catch2/catch.hpp" + +#include "zm_comms.h" +#include + +TEST_CASE("ZM::Pipe basics") { + ZM::Pipe pipe; + + SECTION("setBlocking on non-opened") { + REQUIRE(pipe.setBlocking(true) == false); + REQUIRE(pipe.setBlocking(false) == false); + } + + REQUIRE(pipe.open() == true); + + REQUIRE(pipe.isOpen() == true); + REQUIRE(pipe.isClosed() == false); + REQUIRE(pipe.getReadDesc() != -1); + REQUIRE(pipe.getWriteDesc() != -1); + + SECTION("double open") { + REQUIRE(pipe.open() == true); // is this expected? + } + + SECTION("close") { + REQUIRE(pipe.close() == true); + + REQUIRE(pipe.isOpen() == false); + REQUIRE(pipe.isClosed() == true); + REQUIRE(pipe.getReadDesc() == -1); + REQUIRE(pipe.getWriteDesc() == -1); + + SECTION("double close") { + REQUIRE(pipe.close() == true); + } + + SECTION("setBlocking on closed") { + REQUIRE(pipe.setBlocking(true) == false); + REQUIRE(pipe.setBlocking(false) == false); + } + } + + SECTION("setBlocking") { + REQUIRE(pipe.setBlocking(true) == true); + REQUIRE(pipe.setBlocking(false) == true); + } +} + +TEST_CASE("ZM::Pipe read/write") { + ZM::Pipe pipe; + + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; + + SECTION("read/write on non-opened pipe") { + REQUIRE(pipe.write(msg.data(), msg.size()) == -1); + REQUIRE(pipe.read(rcv.data(), rcv.size()) == -1); + } + + SECTION("read/write on opened pipe") { + REQUIRE(pipe.open() == true); + + REQUIRE(pipe.write(msg.data(), msg.size()) == msg.size()); + REQUIRE(pipe.read(rcv.data(), rcv.size()) == msg.size()); + + REQUIRE(rcv == msg); + } + + SECTION("read/write on closed pipe") { + REQUIRE(pipe.open() == true); + REQUIRE(pipe.close() == true); + + REQUIRE(pipe.write(msg.data(), msg.size()) == -1); + REQUIRE(pipe.read(rcv.data(), rcv.size()) == -1); + } +} + +TEST_CASE("ZM::SockAddrInet") { + ZM::SockAddrInet addr; + REQUIRE(addr.getAddrSize() == sizeof(sockaddr_in)); + + SECTION("resolve") { + addr.resolve(80, ""); + REQUIRE(addr.getDomain() == AF_INET); + + SECTION("newSockAddr from resolved addr") { + ZM::SockAddr *addr2 = ZM::SockAddr::newSockAddr(&addr); + REQUIRE(addr2->getDomain() == AF_INET); + REQUIRE(addr2->getAddrSize() == sizeof(sockaddr_in)); + } + } +} + +TEST_CASE("ZM::SockAddrUnix") { + ZM::SockAddrUnix addr; + REQUIRE(addr.getAddrSize() == sizeof(sockaddr_un)); + + SECTION("resovle") { + addr.resolve("/", ""); + REQUIRE(addr.getDomain() == AF_UNIX); + + SECTION("newSockAddr from resolved addr") { + ZM::SockAddr *addr2 = ZM::SockAddr::newSockAddr(&addr); + REQUIRE(addr2->getDomain() == AF_UNIX); + REQUIRE(addr2->getAddrSize() == sizeof(sockaddr_un)); + } + } +} + +TEST_CASE("ZM::UdpInetSocket basics") { + ZM::UdpInetSocket socket; + REQUIRE(socket.isClosed() == true); + REQUIRE(socket.isOpen() == false); + REQUIRE(socket.isConnected() == false); + REQUIRE(socket.isDisconnected() == false); + + SECTION("bind with host and port") { + REQUIRE(socket.bind(nullptr, "1234") == true); + REQUIRE(socket.isOpen() == true); + REQUIRE(socket.isDisconnected() == true); + REQUIRE(socket.isClosed() == false); + REQUIRE(socket.isConnected() == false); + + SECTION("close") { + REQUIRE(socket.close() == true); + REQUIRE(socket.isClosed() == true); + REQUIRE(socket.isOpen() == false); + REQUIRE(socket.isConnected() == false); + REQUIRE(socket.isDisconnected() == false); + } + } + + SECTION("bind with port") { + REQUIRE(socket.bind("1234") == true); + } + + SECTION("bind with host and port number") { + REQUIRE(socket.bind(nullptr, 1234) == true); + } + + SECTION("bind with port number") { + REQUIRE(socket.bind(1234) == true); + } +} + +TEST_CASE("ZM::UdpInetSocket send/recv") { + ZM::UdpInetSocket srv_socket; + ZM::UdpInetSocket client_socket; + + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; + + SECTION("send/recv on unbound socket") { + REQUIRE(client_socket.send(msg.data(), msg.size()) == -1); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == -1); + } + + SECTION("send/recv") { + REQUIRE(srv_socket.bind("127.0.0.1", "1234") == true); + REQUIRE(srv_socket.isOpen() == true); + + REQUIRE(client_socket.connect("127.0.0.1", "1234") == true); + REQUIRE(client_socket.isConnected() == true); + + REQUIRE(client_socket.send(msg.data(), msg.size()) == msg.size()); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == msg.size()); + + REQUIRE(rcv == msg); + } +} + +TEST_CASE("ZM::UdpUnixSocket basics") { + std::string sock_path = "/tmp/zm.unittest.sock"; + unlink(sock_path.c_str()); // make sure the socket file does not exist + + ZM::UdpUnixSocket socket; + REQUIRE(socket.isClosed() == true); + REQUIRE(socket.isOpen() == false); + REQUIRE(socket.isConnected() == false); + REQUIRE(socket.isDisconnected() == false); + + SECTION("bind") { + REQUIRE(socket.bind(sock_path.c_str()) == true); + REQUIRE(socket.isOpen() == true); + REQUIRE(socket.isDisconnected() == true); + REQUIRE(socket.isClosed() == false); + REQUIRE(socket.isConnected() == false); + + SECTION("close") { + REQUIRE(socket.close() == true); + REQUIRE(socket.isClosed() == true); + REQUIRE(socket.isOpen() == false); + REQUIRE(socket.isConnected() == false); + REQUIRE(socket.isDisconnected() == false); + } + } + + SECTION("connect to unbound socket") { + REQUIRE(socket.connect(sock_path.c_str()) == false); + } +} + +TEST_CASE("ZM::UdpUnixSocket send/recv") { + std::string sock_path = "/tmp/zm.unittest.sock"; + unlink(sock_path.c_str()); // make sure the socket file does not exist + + ZM::UdpUnixSocket srv_socket; + ZM::UdpUnixSocket client_socket; + + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; + + SECTION("send/recv on unbound socket") { + REQUIRE(client_socket.send(msg.data(), msg.size()) == -1); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == -1); + } + + SECTION("send/recv") { + REQUIRE(srv_socket.bind(sock_path.c_str()) == true); + REQUIRE(srv_socket.isOpen() == true); + + REQUIRE(client_socket.connect(sock_path.c_str()) == true); + REQUIRE(client_socket.isConnected() == true); + + REQUIRE(client_socket.send(msg.data(), msg.size()) == msg.size()); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == msg.size()); + + REQUIRE(rcv == msg); + } +} + +TEST_CASE("ZM::TcpInetClient basics") { + ZM::TcpInetClient client; + REQUIRE(client.isClosed() == true); + REQUIRE(client.isOpen() == false); + REQUIRE(client.isConnected() == false); + REQUIRE(client.isDisconnected() == false); + + REQUIRE(client.connect("127.0.0.1", 1234) == false); + REQUIRE(client.isClosed() == true); + REQUIRE(client.isOpen() == false); + REQUIRE(client.isConnected() == false); + REQUIRE(client.isDisconnected() == false); +} + +TEST_CASE("ZM::TcpInetServer basics", "[notCI]") { + ZM::TcpInetServer server; + REQUIRE(server.isClosed() == true); + REQUIRE(server.isOpen() == false); + REQUIRE(server.isConnected() == false); + REQUIRE(server.isDisconnected() == false); + + REQUIRE(server.bind(1234) == true); + REQUIRE(server.isOpen() == true); + REQUIRE(server.isClosed() == false); + REQUIRE(server.isConnected() == false); + REQUIRE(server.isDisconnected() == true); + REQUIRE(server.isListening() == false); + + REQUIRE(server.listen() == true); + REQUIRE(server.isListening() == true); + + SECTION("close") { + REQUIRE(server.close() == true); + REQUIRE(server.isClosed() == true); + REQUIRE(server.isOpen() == false); + REQUIRE(server.isConnected() == false); + REQUIRE(server.isDisconnected() == false); + } +} + +TEST_CASE("ZM::TcpInetClient/Server send/recv", "[notCI]") { + ZM::TcpInetServer server; + ZM::TcpInetClient client; + + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; + + SECTION("send/recv on unbound socket") { + REQUIRE(client.send(msg.data(), msg.size()) == -1); + REQUIRE(server.recv(rcv.data(), rcv.size()) == -1); + } + + SECTION("send/recv") { + REQUIRE(server.bind(1234) == true); + REQUIRE(server.isOpen() == true); + REQUIRE(server.listen() == true); + + REQUIRE(client.connect("127.0.0.1", 1234) == true); + REQUIRE(client.isConnected() == true); + + REQUIRE(server.accept() == true); + + REQUIRE(client.send(msg.data(), msg.size()) == msg.size()); + REQUIRE(server.recv(rcv.data(), rcv.size()) == msg.size()); + + REQUIRE(rcv == msg); + } +} diff --git a/tests/zm_crypt.cpp b/tests/zm_crypt.cpp new file mode 100644 index 000000000..d3a315ef4 --- /dev/null +++ b/tests/zm_crypt.cpp @@ -0,0 +1,78 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "catch2/catch.hpp" + +#include "zm_crypt.h" + +TEST_CASE("JWT validation") { + std::string key = "testsecret"; + + SECTION("Valid token") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxMjM0fQ.94WPmBAVl_83KCI9B3Jq9sNpoOdi0Hm1dR4sc6MCPUA"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == "johndoe"); + REQUIRE(result.second == 1234); + } + + SECTION("Invalid signature") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxMjM0fQ.DhviT6RkDLmbXh5F9zM4l0VbWNPCuKptF6fORv1lBlA"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing user claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTIzNH0.mfi3ZHnqUAPUh5ECxDIkAM9WW9a8HbKrP73LC3yYJmw"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing type claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJpYXQiOjEyMzR9.D4Irs1gHfzO4psRY2xsOdClTg-Sp1kM__mmfNLs7CII"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Wrong type claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoid3JvbmciLCJpYXQiOjEyMzR9.I1Gd50J6mck05vzc_kzjaH4RNjLBaFGpOnie6-PbX28"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } + + SECTION("Missing iat claim") { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJab25lTWluZGVyIiwidXNlciI6ImpvaG5kb2UiLCJ0eXBlIjoid3JvbmcifQ.8iUFOUKJAK5vU8JWKm8D0EOEhm1rJoIulCO11O_Tsp0"; + std::pair result = verifyToken(token, key); + + REQUIRE(result.first == ""); + REQUIRE(result.second == 0); + } +} diff --git a/tests/zm_font.cpp b/tests/zm_font.cpp new file mode 100644 index 000000000..d862c3291 --- /dev/null +++ b/tests/zm_font.cpp @@ -0,0 +1,161 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "catch2/catch.hpp" + +#include "zm_font.h" + +CATCH_REGISTER_ENUM(FontLoadError, + FontLoadError::kOk, + FontLoadError::kFileNotFound, + FontLoadError::kInvalidFile) + +TEST_CASE("FontVariant: construction") { + FontVariant variant; + + SECTION("default construction") { + REQUIRE(variant.GetCharHeight() == 0); + REQUIRE(variant.GetCharWidth() == 0); + } + + SECTION("values in range") { + constexpr uint8 height = 10; + constexpr uint8 width = 10; + constexpr uint8 padding = 2; + std::vector bitmap(FontVariant::kMaxNumCodePoints * height); + + REQUIRE_NOTHROW(variant = FontVariant(height, width, padding, bitmap)); + + REQUIRE(variant.GetCharHeight() == height); + REQUIRE(variant.GetCharWidth() == width); + REQUIRE(variant.GetCodepointsCount() == FontVariant::kMaxNumCodePoints); + } + + SECTION("height out of range") { + constexpr uint8 height = FontVariant::kMaxCharHeight + 1; + constexpr uint8 width = 10; + constexpr uint8 padding = 2; + std::vector bitmap(FontVariant::kMaxNumCodePoints * height); + + REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap)); + } + + SECTION("width out of range") { + constexpr uint8 height = 10; + constexpr uint8 width = FontVariant::kMaxCharWidth + 1; + constexpr uint8 padding = 2; + std::vector bitmap(FontVariant::kMaxNumCodePoints * height); + + REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap)); + } + + SECTION("bitmap of wrong size") { + constexpr uint8 height = 10; + constexpr uint8 width = 10; + constexpr uint8 padding = 2; + std::vector bitmap(FontVariant::kMaxNumCodePoints * height + 1); + + REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap)); + } +} + +TEST_CASE("FontVariant: GetCodepoint") { + constexpr uint8 height = 10; + constexpr uint8 width = 10; + constexpr uint8 padding = 2; + std::vector bitmap(FontVariant::kMaxNumCodePoints * height); + + // fill bitmap for each codepoint alternating with 1 and std::numeric_limits::max() + std::generate(bitmap.begin(), bitmap.end(), + [n = 0, zero = true]() mutable { + if (n == height) { + zero = !zero; + n = 0; + } + n++; + if (zero) { + return static_cast(1); + } else { + return std::numeric_limits::max(); + } + }); + + FontVariant variant(height, width, padding, bitmap); + nonstd::span cp; + + SECTION("in bounds") { + cp = variant.GetCodepoint(0); + REQUIRE(std::all_of(cp.begin(), cp.end(), + [](uint64 l) { return l == 1; }) == true); + + cp = variant.GetCodepoint(1); + REQUIRE(std::all_of(cp.begin(), cp.end(), + [](uint64 l) { return l == std::numeric_limits::max(); }) == true); + } + + SECTION("out-of-bounds: all-zero bitmap") { + cp = variant.GetCodepoint(FontVariant::kMaxNumCodePoints); + REQUIRE(std::all_of(cp.begin(), cp.end(), + [](uint64 l) { return l == 0; }) == true); + } +} + +TEST_CASE("ZmFont: variants not loaded") { + ZmFont font; + + SECTION("returns empty variant") { + FontVariant variant; + REQUIRE_NOTHROW(variant = font.GetFontVariant(0)); + + REQUIRE(variant.GetCharHeight() == 0); + REQUIRE(variant.GetCharWidth() == 0); + REQUIRE(variant.GetCodepoint(0).empty() == true); + } + + SECTION("variant idx out-of-bounds") { + REQUIRE_THROWS(font.GetFontVariant(kNumFontSizes)); + } +} + +TEST_CASE("ZmFont: load font file") { + ZmFont font; + + SECTION("file not found") { + REQUIRE(font.LoadFontFile("does_not_exist.zmfnt") == FontLoadError::kFileNotFound); + } + + SECTION("invalid files") { + REQUIRE(font.LoadFontFile("data/fonts/01_bad_magic.zmfnt") == FontLoadError::kInvalidFile); + REQUIRE(font.LoadFontFile("data/fonts/02_variant_invalid.zmfnt") == FontLoadError::kInvalidFile); + REQUIRE(font.LoadFontFile("data/fonts/03_missing_cps.zmfnt") == FontLoadError::kInvalidFile); + } + + SECTION("valid file") { + REQUIRE(font.LoadFontFile("data/fonts/04_valid.zmfnt") == FontLoadError::kOk); + + uint8 var_idx = GENERATE(range(static_cast::type>(0), kNumFontSizes)); + FontVariant variant = font.GetFontVariant(var_idx); + REQUIRE(variant.GetCharHeight() == 10 + var_idx); + REQUIRE(variant.GetCharWidth() == 10 + var_idx); + + uint8 cp_idx = + GENERATE_COPY(range(static_cast(0), variant.GetCodepointsCount())); + nonstd::span cp = variant.GetCodepoint(cp_idx); + REQUIRE(std::all_of(cp.begin(), cp.end(), + [=](uint64 l) { return l == var_idx; }) == true); + } +} diff --git a/tests/zm_utils.cpp b/tests/zm_utils.cpp new file mode 100644 index 000000000..408b9f43e --- /dev/null +++ b/tests/zm_utils.cpp @@ -0,0 +1,240 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "catch2/catch.hpp" + +#include "zm_utils.h" +#include + +TEST_CASE("Trim") { + REQUIRE(Trim("", "") == ""); + REQUIRE(Trim("test", "") == "test"); + REQUIRE(Trim(" ", "") == " "); + + REQUIRE(Trim("\"test", "\"") == "test"); + REQUIRE(Trim("test\"", "\"") == "test"); + REQUIRE(Trim("\"test\"", "\"") == "test"); + + REQUIRE(Trim("te\"st", "\"") == "te\"st"); + REQUIRE(Trim("\"te\"st\"", "\"") == "te\"st"); +} + +TEST_CASE("TrimSpaces") { + REQUIRE(TrimSpaces(" ") == ""); + + REQUIRE(TrimSpaces("test") == "test"); + REQUIRE(TrimSpaces(" test ") == "test"); + REQUIRE(TrimSpaces(" test ") == "test"); + REQUIRE(TrimSpaces(" test ") == "test"); + REQUIRE(TrimSpaces(" test") == "test"); + REQUIRE(TrimSpaces("\ttest") == "test"); + REQUIRE(TrimSpaces("test\t") == "test"); + REQUIRE(TrimSpaces("\ttest\t") == "test"); + REQUIRE(TrimSpaces(" test\t") == "test"); + REQUIRE(TrimSpaces("\ttest ") == "test"); + REQUIRE(TrimSpaces("\t test \t") == "test"); + + REQUIRE(TrimSpaces("\t te st \t") == "te st"); +} + +TEST_CASE("ReplaceAll") { + REQUIRE(ReplaceAll("", "", "") == ""); + + REQUIRE(ReplaceAll("a", "", "b") == "a"); + REQUIRE(ReplaceAll("a", "a", "b") == "b"); + REQUIRE(ReplaceAll("a", "b", "c") == "a"); + + REQUIRE(ReplaceAll("aa", "a", "b") == "bb"); + REQUIRE(ReplaceAll("aba", "a", "c") == "cbc"); + + REQUIRE(ReplaceAll("aTOKENa", "TOKEN", "VAL") == "aVALa"); + REQUIRE(ReplaceAll("aTOKENaTOKEN", "TOKEN", "VAL") == "aVALaVAL"); +} + +TEST_CASE("StartsWith") { + REQUIRE(StartsWith("", "") == true); + + REQUIRE(StartsWith("test", "test") == true); + REQUIRE(StartsWith("test=abc", "test") == true); + REQUIRE(StartsWith(" test=abc", "test") == false); +} + +TEST_CASE("Split (char delimiter)") { + std::vector items = Split("", ' '); + REQUIRE(items == std::vector{""}); + + items = Split("abc def ghi", ' '); + REQUIRE(items == std::vector{"abc", "def", "ghi"}); + + items = Split("abc,def,,ghi", ','); + REQUIRE(items == std::vector{"abc", "def", "", "ghi"}); +} + +TEST_CASE("Split (string delimiter)") { + std::vector items; + + items = Split("", ""); + REQUIRE(items == std::vector{""}); + + items = Split("", " "); + REQUIRE(items == std::vector{""}); + + items = Split("", " \t"); + REQUIRE(items == std::vector{""}); + + items = Split("", " \t"); + REQUIRE(items == std::vector{""}); + + items = Split(" ", " "); + REQUIRE(items.size() == 0); + + items = Split(" ", " "); + REQUIRE(items.size() == 0); + + items = Split(" ", " \t"); + REQUIRE(items.size() == 0); + + items = Split("a b", ""); + REQUIRE(items == std::vector{"a b"}); + + items = Split("a b", " "); + REQUIRE(items == std::vector{"a", "b"}); + + items = Split("a \tb", " \t"); + REQUIRE(items == std::vector{"a", "b"}); + + items = Split(" a \tb ", " \t"); + REQUIRE(items == std::vector{"a", "b"}); + + items = Split(" a=b ", "="); + REQUIRE(items == std::vector{" a", "b "}); + + items = Split(" a=b ", " ="); + REQUIRE(items == std::vector{"a", "b"}); + + items = Split("a b c", " ", 2); + REQUIRE(items == std::vector{"a", "b c"}); +} + +TEST_CASE("Join") { + REQUIRE(Join({}, "") == ""); + REQUIRE(Join({}, " ") == ""); + REQUIRE(Join({""}, "") == ""); + REQUIRE(Join({"a"}, "") == "a"); + REQUIRE(Join({"a"}, ",") == "a"); + REQUIRE(Join({"a", "b"}, ",") == "a,b"); + REQUIRE(Join({"a", "b"}, "") == "ab"); +} + +TEST_CASE("Base64Encode") { + REQUIRE(Base64Encode("") == ""); + REQUIRE(Base64Encode("f") == "Zg=="); + REQUIRE(Base64Encode("fo") == "Zm8="); + REQUIRE(Base64Encode("foo") == "Zm9v"); + REQUIRE(Base64Encode("foob") == "Zm9vYg=="); + REQUIRE(Base64Encode("fooba") == "Zm9vYmE="); + REQUIRE(Base64Encode("foobar") == "Zm9vYmFy"); +} + +TEST_CASE("ZM::clamp") { + REQUIRE(ZM::clamp(1, 0, 2) == 1); + REQUIRE(ZM::clamp(3, 0, 2) == 2); + REQUIRE(ZM::clamp(-1, 0, 2) == 0); +} + +TEST_CASE("UriDecode") { + REQUIRE(UriDecode("abcABC123-_.~%21%28%29%26%3d%20") == "abcABC123-_.~!()&= "); + REQUIRE(UriDecode("abcABC123-_.~%21%28%29%26%3d+") == "abcABC123-_.~!()&= "); +} + +TEST_CASE("QueryString") { + SECTION("no value") { + std::stringstream str("name1="); + QueryString qs(str); + + REQUIRE(qs.size() == 1); + REQUIRE(qs.has("name1") == true); + + const QueryParameter *p = qs.get("name1"); + REQUIRE(p != nullptr); + REQUIRE(p->name() == "name1"); + REQUIRE(p->size() == 0); + } + + SECTION("no value and ampersand") { + std::stringstream str("name1=&"); + QueryString qs(str); + + REQUIRE(qs.size() == 1); + REQUIRE(qs.has("name1") == true); + + const QueryParameter *p = qs.get("name1"); + REQUIRE(p != nullptr); + REQUIRE(p->name() == "name1"); + REQUIRE(p->size() == 0); + } + + SECTION("one parameter, one value") { + std::stringstream str("name1=value1"); + QueryString qs(str); + + REQUIRE(qs.size() == 1); + REQUIRE(qs.has("name1") == true); + + const QueryParameter *p = qs.get("name1"); + REQUIRE(p != nullptr); + REQUIRE(p->name() == "name1"); + REQUIRE(p->size() == 1); + REQUIRE(p->values()[0] == "value1"); + } + + SECTION("one parameter, multiple values") { + std::stringstream str("name1=value1&name1=value2"); + QueryString qs(str); + + REQUIRE(qs.size() == 1); + REQUIRE(qs.has("name1") == true); + + const QueryParameter *p = qs.get("name1"); + REQUIRE(p != nullptr); + REQUIRE(p->name() == "name1"); + REQUIRE(p->size() == 2); + REQUIRE(p->values()[0] == "value1"); + REQUIRE(p->values()[1] == "value2"); + } + + SECTION("multiple parameters, multiple values") { + std::stringstream str("name1=value1&name2=value2"); + QueryString qs(str); + + REQUIRE(qs.size() == 2); + REQUIRE(qs.has("name1") == true); + REQUIRE(qs.has("name2") == true); + + const QueryParameter *p1 = qs.get("name1"); + REQUIRE(p1 != nullptr); + REQUIRE(p1->name() == "name1"); + REQUIRE(p1->size() == 1); + REQUIRE(p1->values()[0] == "value1"); + + const QueryParameter *p2 = qs.get("name2"); + REQUIRE(p2 != nullptr); + REQUIRE(p2->name() == "name2"); + REQUIRE(p2->size() == 1); + REQUIRE(p2->values()[0] == "value2"); + } +} diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index d6107c5a9..289158c77 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -80,7 +80,7 @@ fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,focal" + DISTROS="xenial,bionic,focal,groovy,hirsute" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; @@ -300,7 +300,7 @@ EOF DEBUILD="debuild -i -us -uc -b" else # Source build, don't need build depends. - DEBUILD="debuild -S -sa" + DEBUILD="debuild -S -sa -sd" fi; fi; if [ "$DEBSIGN_KEYID" != "" ]; then diff --git a/utils/mk_bigfont.pl b/utils/mk_bigfont.pl index 41ebb34bf..96e6881c3 100755 --- a/utils/mk_bigfont.pl +++ b/utils/mk_bigfont.pl @@ -48,18 +48,18 @@ while (my $line = ) { $in_head-- if $line =~ /^$/ and $in_head; next while $in_head; unless ($line =~ /^\s+(0x..), \/\* (........)/) { - $line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; + #$line =~ s/static unsigned char fontdata/static unsigned int bigfontdata/; print $line; next; } my $code = $1; my $bincode = $2; $bincode = "$1$1$2$2$3$3$4$4$5$5$6$6$7$7$8$8" if $bincode =~ /(.)(.)(.)(.)(.)(.)(.)(.)$/; - $bincode =~ s/ /1/g; - my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); - my $hexcode = sprintf("%#x", $intcode); - $hexcode =~ s/^0$/0x0/; - $bincode =~ s/1/ /g; + #$bincode =~ s/ /1/g; + #my $intcode = unpack("N", pack("B32", substr("0" x 32 . $bincode, -32))); + #my $hexcode = sprintf("%#x", $intcode); + #$hexcode =~ s/^0$/0x0/; + #$bincode =~ s/1/ /g; print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); print sprintf("\t%10s, /* %s */\n", $hexcode, $bincode); } diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 1b2eafd8f..f2d629dbd 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -94,12 +94,12 @@ commonprep () { # The rpm specfile requires we download each submodule as a tarball then manually move it into place # Might as well do this for Debian as well, rather than git submodule init - CRUDVER="3.1.0-zm" + CRUDVER="3.2.0" if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then echo "Found existing Crud ${CRUDVER} tarball..." else echo "Retrieving Crud ${CRUDVER} submodule..." - curl -L https://github.com/ZoneMinder/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz + curl -L https://github.com/FriendsOfCake/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz if [ $? -ne 0 ]; then echo "ERROR: Crud tarball retreival failed..." exit 1 @@ -117,6 +117,18 @@ commonprep () { exit 1 fi fi + + RTSPVER="cd7fd49becad6010a1b8466bfebbd93999a39878" + if [ -e "build/RtspServer-${RTSPVER}.tar.gz" ]; then + echo "Found existing RtspServer ${RTSPVER} tarball..." + else + echo "Retrieving RTSP ${RTSPVER} submodule..." + curl -L https://github.com/ZoneMinder/RtspServer/archive/${RTSPVER}.tar.gz > build/RtspServer-${RTSPVER}.tar.gz + if [ $? -ne 0 ]; then + echo "ERROR: RtspServer tarball retreival failed..." + exit 1 + fi + fi } # Uncompress the submodule tarballs and move them into place @@ -137,6 +149,14 @@ movecrud () { rmdir web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior fi + if [ -e "dep/RtspServer/CMakeLists.txt" ]; then + echo "RtspServer already installed..." + else + echo "Unpacking RtspServer..." + tar -xzf build/RtspServer-${RTSPVER}.tar.gz + rmdir dep/RtspServer + mv -f RtspServer-${RTSPVER} dep/RtspServer + fi } # previsouly part of installzm.sh @@ -200,7 +220,9 @@ setdebpkgname () { # Set VERSION to {zm version}~{today's date}.{number of commits} e.g. 1.31.0~20170605.82 # Set RELEASE to the packpack DIST variable e.g. Trusty - export VERSION="${versionfile}~${thedate}.${numcommits}" + if [ "" == "$VERSION" ]; then + export VERSION="${versionfile}~${thedate}.${numcommits}" + fi export RELEASE="${DIST}" checkvars @@ -347,7 +369,7 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia setdebpkgname movecrud - if [ "${DIST}" == "focal" ] || [ "${DIST}" == "buster" ]; then + if [ "${DIST}" == "focal" ] || [ "${DIST}" == "groovy" ] || [ "${DIST}" == "hirsuit" ] || [ "${DIST}" == "buster" ]; then ln -sfT distros/ubuntu2004 debian elif [ "${DIST}" == "beowulf" ]; then ln -sfT distros/beowulf debian diff --git a/version b/version index 21cbe96c2..b538e3240 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.35.6 +1.35.28 diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index b3d097739..9676f1cfa 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -2,27 +2,18 @@ # Process the api subdirectory add_subdirectory(api) -# Process the tools/mootools subdirectory -add_subdirectory(tools/mootools) - # Create files from the .in files configure_file(includes/config.php.in "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" @ONLY) # Install the web files -install(DIRECTORY vendor api ajax css fonts graphics includes js lang skins tools views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) +install(DIRECTORY vendor api ajax css fonts graphics includes js lang skins views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) install(FILES index.php robots.txt DESTINATION "${ZM_WEBDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" DESTINATION "${ZM_WEBDIR}/includes") # Install the api config files (if its not in the source directory) if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - -# Install the mootools symlinks (if its not in the source directory) -if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-core.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tools/mootools/mootools-more.js" DESTINATION "${ZM_WEBDIR}/tools/mootools") -endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") +endif() diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php index e7048bb4a..4a6809cf9 100644 --- a/web/ajax/add_monitors.php +++ b/web/ajax/add_monitors.php @@ -1,14 +1,22 @@ set(array( 'StorageId' => 1, 'ServerId' => 'auto', - 'Function' => 'Record', + 'Function' => 'Mocord', 'Type' => 'Ffmpeg', 'Enabled' => '1', 'Colour' => '4', // 32bit - 'PreEventCount' => 0, + 'ImageBufferCount' => '20', + 'WarmupCount' => '0', + 'PreEventCount' => '0', + 'StreamReplayBuffer' => '0', + 'SaveJPEGs' => '4', + 'VideoWriter' => '1', + 'MaxFPS' => '20', + 'AlarmMaxFPS' => '20', ) ); function probe( &$url_bits ) { @@ -19,11 +27,11 @@ function probe( &$url_bits ) { $cam_list_html = file_get_contents('http://'.$url_bits['host'].':5000/monitoring/'); if ( $cam_list_html ) { - ZM\Logger::Debug("Have content at port 5000/monitoring"); + ZM\Debug("Have content at port 5000/monitoring"); $matches_count = preg_match_all( '/([^<]+)<\/a>/', $cam_list_html, $cam_list ); - ZM\Logger::Debug(print_r($cam_list,true)); + ZM\Debug(print_r($cam_list,true)); } if ( $matches_count ) { for( $index = 0; $index < $matches_count; $index ++ ) { @@ -33,7 +41,7 @@ function probe( &$url_bits ) { if ( ! isset($new_stream['scheme'] ) ) $new_stream['scheme'] = 'http'; $available_streams[] = $new_stream; -ZM\Logger::Debug("Have new stream " . print_r($new_stream,true) ); +ZM\Debug("Have new stream " . print_r($new_stream,true) ); } } else { ZM\Info('No matches'); diff --git a/web/ajax/controlcaps.php b/web/ajax/controlcaps.php new file mode 100644 index 000000000..9e655c128 --- /dev/null +++ b/web/ajax/controlcaps.php @@ -0,0 +1,33 @@ + diff --git a/web/ajax/device.php b/web/ajax/device.php new file mode 100644 index 000000000..71e76d8f7 --- /dev/null +++ b/web/ajax/device.php @@ -0,0 +1,47 @@ + diff --git a/src/zm_threaddata.cpp b/web/ajax/devices.php similarity index 58% rename from src/zm_threaddata.cpp rename to web/ajax/devices.php index 6b25d5714..25d17af49 100644 --- a/src/zm_threaddata.cpp +++ b/web/ajax/devices.php @@ -1,21 +1,38 @@ +; -template class ThreadData; +// Device view actions +if ( !canEdit('Devices') ) { + ajaxError('Insufficient permissions for user '.$user['Username']); + return; +} + +if ( $action == 'delete' ) { + if ( isset($_REQUEST['markDids']) ) { + foreach( $_REQUEST['markDids'] as $markDid ) { + dbQuery('DELETE FROM Devices WHERE Id=?', array($markDid)); + } + } + ajaxResponse(); +} else { + ajaxError('Unrecognised action '.$_REQUEST['action']); +} + +?> diff --git a/web/ajax/events.php b/web/ajax/events.php index 16c777ebf..8a8e3a4f1 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -1,41 +1,266 @@ addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); +} + +// Search contains a user entered string to search on +$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; + +// Advanced search contains an array of "column name" => "search text" pairs +// Bootstrap table sends json_ecoded array, which we must decode +$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); + +// Sort specifies the name of the column to sort on +$sort = 'StartDateTime'; +if ( isset($_REQUEST['sort']) ) { + $sort = $_REQUEST['sort']; +} + +// Offset specifies the starting row to return, used for pagination +$offset = 0; +if ( isset($_REQUEST['offset']) ) { + if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } +} + +// Order specifies the sort direction, either asc or desc +$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; + +// Limit specifies the number of rows to return +// Set the default to 0 for events view, to prevent an issue with ALL pagination +$limit = 0; +if ( isset($_REQUEST['limit']) ) { + if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); + } else { + $limit = $_REQUEST['limit']; + } +} + +// +// MAIN LOOP +// + +switch ( $task ) { + case 'archive' : + foreach ( $eids as $eid ) archiveRequest($task, $eid); + break; + case 'unarchive' : + # The idea is that anyone can archive, but only people with Event Edit permission can unarchive.. + if ( !canEdit('Events') ) { + ajaxError('Insufficient permissions for user '.$user['Username']); + return; + } + foreach ( $eids as $eid ) archiveRequest($task, $eid); + break; + case 'delete' : + if ( !canEdit('Events') ) { + ajaxError('Insufficient permissions for user '.$user['Username']); + return; + } + + foreach ( $eids as $eid ) $data[] = deleteRequest($eid); + break; + case 'query' : + $data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit); + break; + default : + ZM\Fatal("Unrecognised task '$task'"); +} // end switch task + +ajaxResponse($data); + +// +// FUNCTION DEFINITIONS +// + +function archiveRequest($task, $eid) { + $archiveVal = ($task == 'archive') ? 1 : 0; + dbQuery( + 'UPDATE Events SET Archived = ? WHERE Id = ?', + array($archiveVal, $eid) + ); +} + +function deleteRequest($eid) { $message = array(); + $event = new ZM\Event($eid); + if ( !$event->Id() ) { + $message[] = array($eid=>'Event not found.'); + } else if ( $event->Archived() ) { + $message[] = array($eid=>'Event is archived, cannot delete it.'); + } else { + $event->delete(); + } + + return $message; +} - foreach ( $_REQUEST['eids'] as $eid ) { +function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { - switch ( $_REQUEST['action'] ) { - case 'archive' : - case 'unarchive' : - $archiveVal = ($_REQUEST['action'] == 'archive') ? 1 : 0; - dbQuery( - 'UPDATE Events SET Archived = ? WHERE Id = ?', - array($archiveVal, $eid) - ); - break; - case 'delete' : - $event = new ZM\Event($eid); - if ( !$event->Id() ) { - $message[] = array($eid=>'Event not found.'); - } else if ( $event->Archived() ) { - $message[] = array($eid=>'Event is archived, cannot delete it.'); - } else { - $event->delete(); + $data = array( + 'total' => 0, + 'totalNotFiltered' => 0, + 'rows' => array(), + 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG) + ); + + $failed = !$filter->test_pre_sql_conditions(); + if ( $failed ) { + ZM\Debug('Pre conditions failed, not doing sql'); + return $data; + } + + // Put server pagination code here + // The table we want our data from + $table = 'Events'; + + // The names of the dB columns in the events table we are interested in + $columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace'); + + // The names of columns shown in the event view that are NOT dB columns in the database + $col_alt = array('Monitor', 'Storage'); + + if ( !in_array($sort, array_merge($columns, $col_alt)) ) { + ZM\Error('Invalid sort field: ' . $sort); + $sort = 'Id'; + } + + $values = array(); + $likes = array(); + $where = $filter->sql()?' WHERE ('.$filter->sql().')' : ''; + + $sort = $sort == 'Monitor' ? 'M.Name' : 'E.'.$sort; + $col_str = 'E.*, M.Name AS Monitor'; + $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.' ORDER BY '.$sort.' '.$order; + + $storage_areas = ZM\Storage::find(); + $StorageById = array(); + foreach ( $storage_areas as $S ) { + $StorageById[$S->Id()] = $S; + } + + $unfiltered_rows = array(); + $event_ids = array(); + + ZM\Debug('Calling the following sql query: ' .$sql); + $query = dbQuery($sql, $values); + if ( $query ) { + while ( $row = dbFetchNext($query) ) { + $event = new ZM\Event($row); + $event->remove_from_cache(); + if ( !$filter->test_post_sql_conditions($event) ) { + continue; } - break; - } // end switch action - } // end foreach - ajaxResponse($message); -} // end if canEdit('Events') + $event_ids[] = $event->Id(); + $unfiltered_rows[] = $row; + } # end foreach row + } -ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user '.$user['Username']); + ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); + + $filtered_rows = null; + + if ( count($advsearch) or $search != '' ) { + $search_filter = new ZM\Filter(); + $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids)); + + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + if ( count($advsearch) ) { + $terms = array(); + foreach ( $advsearch as $col=>$text ) { + $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); + } # end foreach col in advsearch + $terms[0]['obr'] = 1; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter->addTerms($terms); + } else if ( $search != '' ) { + $search = '%' .$search. '%'; + $terms = array(); + foreach ( $columns as $col ) { + $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); + } + $terms[0]['obr'] = 1; + $terms[0]['cnj'] = 'and'; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR')); + } # end if search + + $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; + ZM\Debug('Calling the following sql query: ' .$sql); + $filtered_rows = dbFetchAll($sql); + ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.'); + } else { + $filtered_rows = $unfiltered_rows; + } # end if search_filter->terms() > 1 + + $returned_rows = array(); + foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { + $event = new ZM\Event($row); + + $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); + $imgSrc = $event->getThumbnailSrc(array(), '&'); + $streamSrc = $event->getStreamSrc(array( + 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&'); + + // Modify the row data as needed + $row['imgHtml'] = 'Event '.$event->Id().''; + $row['Name'] = validHtmlStr($row['Name']); + $row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No'); + $row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No'); + $row['Cause'] = validHtmlStr($row['Cause']); + $row['StartDateTime'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['StartDateTime'])); + $row['EndDateTime'] = $row['EndDateTime'] ? strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['EndDateTime'])) : null; + $row['Length'] = gmdate('H:i:s', $row['Length'] ); + $row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default'; + $row['Notes'] = nl2br(htmlspecialchars($row['Notes'])); + $row['DiskSpace'] = human_filesize($event->DiskSpace()); + $returned_rows[] = $row; + } # end foreach row matching search + + $data['rows'] = $returned_rows; + + # totalNotFiltered must equal total, except when either search bar has been used + $data['totalNotFiltered'] = count($unfiltered_rows); + if ( $search != '' || count($advsearch) ) { + $data['total'] = count($filtered_rows); + } else { + $data['total'] = $data['totalNotFiltered']; + } + + return $data; +} ?> diff --git a/web/ajax/frames.php b/web/ajax/frames.php new file mode 100644 index 000000000..e129200e7 --- /dev/null +++ b/web/ajax/frames.php @@ -0,0 +1,204 @@ + "search text" pairs +// Bootstrap table sends json_ecoded array, which we must decode +$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); + +// Sort specifies the name of the column to sort on +$sort = 'FrameId'; +if ( isset($_REQUEST['sort']) ) { + $sort = $_REQUEST['sort']; +} + +// Offset specifies the starting row to return, used for pagination +$offset = 0; +if ( isset($_REQUEST['offset']) ) { + if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } +} + +// Order specifies the sort direction, either asc or desc +$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; + +// Limit specifies the number of rows to return +$limit = 0; +if ( isset($_REQUEST['limit']) ) { + if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); + } else { + $limit = $_REQUEST['limit']; + } +} + +// +// MAIN LOOP +// + +// Only one supported task at the moment +switch ( $task ) { + case 'query' : + $data = queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit); + break; + default : + ZM\Fatal("Unrecognised task '$task'"); +} // end switch task + +ajaxResponse($data); + +// +// FUNCTION DEFINITIONS +// + +function queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit) { + + $data = array( + 'total' => 0, + 'totalNotFiltered' => 0, + 'rows' => array(), + 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG) + ); + + // The names of the dB columns in the events table we are interested in + $columns = array('FrameId', 'Type', 'TimeStamp', 'Delta', 'Score'); + + if ( !in_array($sort, $columns) ) { + ZM\Error('Invalid sort field: ' . $sort); + $sort = 'FrameId'; + } + + $Event = new ZM\Event($eid); + $Monitor = $Event->Monitor(); + $values = array(); + $likes = array(); + $where = 'WHERE EventId = '.$eid; + + $sql = 'SELECT * FROM `Frames` '.$where.' ORDER BY '.$sort.' '.$order; + + //ZM\Debug('Calling the following sql query: ' .$sql); + + $unfiltered_rows = array(); + $frame_ids = array(); + require_once('includes/Frame.php'); + foreach ( dbFetchAll($sql, NULL, $values) as $row ) { + $frame = new ZM\Frame($row); + $frame_ids[] = $frame->Id(); + $unfiltered_rows[] = $row; + } + + ZM\Debug('Have ' . count($unfiltered_rows) . ' frames matching base filter.'); + + $filtered_rows = null; + require_once('includes/Filter.php'); + if ( count($advsearch) or $search != '' ) { + $search_filter = new ZM\Filter(); + $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'FrameId', 'op'=>'IN', 'val'=>$frame_ids)); + + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + if ( count($advsearch) ) { + $terms = array(); + foreach ( $advsearch as $col=>$text ) { + $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); + } # end foreach col in advsearch + $terms[0]['obr'] = 1; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter->addTerms($terms); + } else if ( $search != '' ) { + $search = '%' .$search. '%'; + $terms = array(); + foreach ( $columns as $col ) { + $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); + } + $terms[0]['obr'] = 1; + $terms[0]['cnj'] = 'and'; + $terms[count($terms)-1]['cbr'] = 1; + $search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR')); + } # end if search + + $sql = 'SELECT * FROM `Frames` WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; + $filtered_rows = dbFetchAll($sql); + ZM\Debug('Have ' . count($filtered_rows) . ' frames matching search filter.'); + } else { + $filtered_rows = $unfiltered_rows; + } # end if search_filter->terms() > 1 + + $returned_rows = array(); + foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { + if ( ZM_WEB_LIST_THUMBS ) { + + # Build the path to the potential analysis image + $analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $row['FrameId']); + $analPath = $Event->Path().'/'.$analImage; + $alarmFrame = $row['Type'] == 'Alarm'; + $hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath); + + # Our base img source component, which we will add on to + $base_img_src = '?view=image&fid=' .$row['Id']; + + # if an analysis images exists, use it as the thumbnail + if ( $hasAnalImage ) $base_img_src .= '&show=analyse'; + + # Build the subcomponents needed for the image source + $ratio_factor = $Monitor->ViewHeight() / $Monitor->ViewWidth(); + $thmb_width = ZM_WEB_LIST_THUMB_WIDTH ? 'width='.ZM_WEB_LIST_THUMB_WIDTH : ''; + $thmb_height = 'height="'.( ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor ) .'"'; + $thmb_fn = 'filename=' .$Event->MonitorId(). '_' .$row['EventId']. '_' .$row['FrameId']. '.jpg'; + + # Assemble the scaled and unscaled image source image source components + $img_src = join('&', array_filter(array($base_img_src, $thmb_width, $thmb_height, $thmb_fn))); + $full_img_src = join('&', array_filter(array($base_img_src, $thmb_fn))); + + # finally, we assemble the the entire thumbnail img src structure, whew + $row['Thumbnail'] = ''; + } + $returned_rows[] = $row; + } # end foreach row matching search + + $data['rows'] = $returned_rows; + + # totalNotFiltered must equal total, except when either search bar has been used + $data['totalNotFiltered'] = count($unfiltered_rows); + if ( $search != '' || count($advsearch) ) { + $data['total'] = count($filtered_rows); + } else { + $data['total'] = $data['totalNotFiltered']; + } + + return $data; +} diff --git a/web/ajax/log.php b/web/ajax/log.php index d5483b7ca..c5181e49b 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,455 +1,184 @@ 'web_js')); + + $string = $_POST['message']; + + $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; + if ( !empty($_POST['line']) ) { + $line = validInt($_POST['line']); + } else { + $line = NULL; + } + + $levels = array_flip(ZM\Logger::$codes); + if ( !isset($levels[$_POST['level']]) ) { + ZM\Panic('Unexpected logger level '.$_POST['level']); + } + $level = $levels[$_POST['level']]; + ZM\Logger::fetch()->logPrint($level, $string, $file, $line); + } else { + ZM\Error('Invalid log create: '.print_r($_POST, true)); + } +} + +function queryRequest() { + + // Offset specifies the starting row to return, used for pagination + $offset = 0; + if ( isset($_REQUEST['offset']) ) { + if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { + ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); + } else { + $offset = $_REQUEST['offset']; + } + } + + // Limit specifies the number of rows to return $limit = 100; if ( isset($_REQUEST['limit']) ) { - if ( ( !is_integer($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { - ZM\Error('Invalid value for limit ' . $_REQUEST['limit']); + if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { + ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); } else { $limit = $_REQUEST['limit']; } } - $sortField = 'TimeKey'; - if ( isset($_REQUEST['sortField']) ) { - if ( !in_array($_REQUEST['sortField'], $filterFields) and ( $_REQUEST['sortField'] != 'TimeKey' ) ) { - ZM\Error('Invalid sort field ' . $_REQUEST['sortField']); - } else { - $sortField = $_REQUEST['sortField']; - } - } - $sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc'; - $filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array(); + // The table we want our data from + $table = 'Logs'; - $sql = $action.' FROM Logs'; - $where = array(); - $values = array(); - if ( $minTime ) { - $where[] = 'TimeKey > ?'; - $values[] = $minTime; - } elseif ( $maxTime ) { - $where[] = 'TimeKey < ?'; - $values[] = $maxTime; + // The names of the dB columns in the log table we are interested in + $columns = array('TimeKey', 'Component', 'ServerId', 'Pid', 'Code', 'Message', 'File', 'Line'); + + // The names of columns shown in the log view that are NOT dB columns in the database + $col_alt = array('DateTime', 'Server'); + + $sort = 'TimeKey'; + if ( isset($_REQUEST['sort']) ) { + $sort = $_REQUEST['sort']; + if ( $sort == 'DateTime' ) $sort = 'TimeKey'; + } + if ( !in_array($sort, array_merge($columns, $col_alt)) ) { + ZM\Error('Invalid sort field: ' . $sort); + return; } - foreach ( $filter as $field=>$value ) { - if ( !in_array($field, $filterFields) ) { - ZM\Error("'$field' is not in valid filter fields " . print_r($filterField, true)); - continue; - } - if ( $field == 'Level' ) { - $where[] = $field.' <= ?'; - $values[] = $value; - } else { - $where[] = $field.' = ?'; - $values[] = $value; - } - } - if ( count($where) ) - $sql.= ' WHERE '.join(' AND ', $where); - $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; + // Order specifies the sort direction, either asc or desc + $order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; - return array('sql'=>$sql, 'values'=>$values); -} # function buildLogQuery($action) + $col_str = implode(', ', $columns); + $data = array(); + $query = array(); + $query['values'] = array(); + $likes = array(); + $where = ''; + // There are two search bars in the log view, normal and advanced + // Making an exuctive decision to ignore the normal search, when advanced search is in use + // Alternatively we could try to do both + // + // Advanced search contains an array of "column name" => "search text" pairs + // Bootstrap table sends json_ecoded array, which we must decode + $advsearch = isset($_REQUEST['filter']) ? json_decode($_REQUEST['filter'], JSON_OBJECT_AS_ARRAY) : array(); + // Search contains a user entered string to search on + $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; + if ( count($advsearch) ) { -switch ( $_REQUEST['task'] ) { - case 'create' : - { - // Silently ignore bogus requests - if ( !empty($_POST['level']) && !empty($_POST['message']) ) { - ZM\logInit(array('id'=>'web_js')); - - $string = $_POST['message']; - - $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; - if ( !empty($_POST['line']) ) { - $line = validInt($_POST['line']); - } else { - $line = NULL; + foreach ( $advsearch as $col=>$text ) { + if ( !in_array($col, array_merge($columns, $col_alt)) ) { + ZM\Error("'$col' is not a searchable column name"); + continue; } - - $levels = array_flip(ZM\Logger::$codes); - if ( !isset($levels[$_POST['level']]) ) { - ZM\Panic('Unexpected logger level '.$_POST['level']); - } - $level = $levels[$_POST['level']]; - ZM\Logger::fetch()->logPrint($level, $string, $file, $line); - } else { - ZM\Error('Invalid log create: '.print_r($_POST, true)); + // Don't use wildcards on advanced search + //$text = '%' .$text. '%'; + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $text); } - ajaxResponse(); - break; + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; + + } else if ( $search != '' ) { + + $search = '%' .$search. '%'; + foreach ( $columns as $col ) { + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $search); + } + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; + } + + $query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?'; + array_push($query['values'], $offset, $limit); + + $data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table, 'Total'); + if ( $search != '' || count($advsearch) ) { + $data['total'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table.$where , 'Total', $wherevalues); + } else { + $data['total'] = $data['totalNotFiltered']; } - case 'delete' : - { - if ( !canEdit('System') ) - ajaxError('Insufficient permissions to delete log entries'); - $query = buildLogQuery('DELETE'); - $result = dbQuery($query['sql'], $query['values']); - ajaxResponse(array('result'=>'Ok', 'deleted'=>$result->rowCount())); + $rows = array(); + $results = dbFetchAll($query['sql'], NULL, $query['values']); + + foreach ( $results as $row ) { + $row['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($row['TimeKey'])); + $Server = ZM\Server::find_one(array('Id'=>$row['ServerId'])); + + $row['Server'] = $Server ? $Server->Name() : ''; + // First strip out any html tags + // Second strip out all characters that are not ASCII 32-126 (yes, 126) + $row['Message'] = preg_replace('/[^\x20-\x7E]/', '', strip_tags($row['Message'])); + $rows[] = $row; } - case 'query' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to view log entries'); - $total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total'); - $query = buildLogQuery('SELECT *'); + $data['rows'] = $rows; + $data['logstate'] = logState(); + $data['updated'] = preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG); - global $Servers; - if ( !$Servers ) - $Servers = ZM\Server::find(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $Servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } - - $logs = array(); - $options = array(); - - foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { - - $log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; - $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']); - foreach ( $filterFields as $field ) { - if ( !isset($options[$field]) ) - $options[$field] = array(); - $value = $log[$field]; - - if ( $field == 'Level' ) { - if ( $value <= ZM\Logger::INFO ) - $options[$field][$value] = ZM\Logger::$codes[$value]; - else - $options[$field][$value] = 'DB'.$value; - } else if ( $field == 'ServerId' ) { - $options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : ''; - } else if ( isset($log[$field]) ) { - $options[$field][$log[$field]] = $value; - } - } - $logs[] = $log; - } # end foreach log db row - - foreach ( $options as $field => $values ) { - asort($options[$field]); - } - - $available = count($logs); - ajaxResponse(array( - 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG), - 'total' => $total, - 'available' => isset($available) ? $available : $total, - 'logs' => $logs, - 'state' => logState(), - 'options' => $options, - )); - break; - } - case 'export' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to export logs'); - - $minTime = isset($_POST['minTime']) ? $_POST['minTime'] : NULL; - $maxTime = isset($_POST['maxTime']) ? $_POST['maxTime'] : NULL; - if ( !is_null($minTime) && !is_null($maxTime) && ($minTime > $maxTime) ) { - $tempTime = $minTime; - $minTime = $maxTime; - $maxTime = $tempTime; - } - //$limit = isset($_POST['limit'])?$_POST['limit']:1000; - $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = 'TimeKey'; - if ( isset($_POST['sortField']) ) { - if ( !in_array($_POST['sortField'], $filterFields) and ($_POST['sortField'] != 'TimeKey') ) { - ZM\Error('Invalid sort field '.$_POST['sortField']); - } else { - $sortField = $_POST['sortField']; - } - } - $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc' : 'desc'; - - global $Servers; - if ( !$Servers ) - $Servers = ZM\Server::find(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } - - $sql = 'SELECT * FROM Logs'; - $where = array(); - $values = array(); - if ( $minTime ) { - if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) { - # This handles sub second precision - $minTime = strtotime($matches[1]).$matches[2]; - } else { - $minTime = strtotime($minTime); - } - $where[] = 'TimeKey >= ?'; - $values[] = $minTime; - } - if ( $maxTime ) { - if ( preg_match('/(.+)(\.\d+)/', $maxTime, $matches) ) { - $maxTime = strtotime($matches[1]).$matches[2]; - } else { - $maxTime = strtotime($maxTime); - } - $where[] = 'TimeKey <= ?'; - $values[] = $maxTime; - } - foreach ( $filter as $field=>$value ) { - if ( $value != '' ) { - if ( $field == 'Level' ) { - $where[] = $field.' <= ?'; - $values[] = $value; - } else { - $where[] = $field.' = ?'; - $values[] = $value; - } - } - } - if ( count($where) ) - $sql.= ' WHERE '.join(' AND ', $where); - $sql .= ' ORDER BY '.$sortField.' '.$sortOrder; - //$sql .= " limit ".dbEscape($limit); - $format = isset($_POST['format']) ? $_POST['format'] : 'text'; - switch ( $format ) { - case 'text' : - $exportExt = 'txt'; - break; - case 'tsv' : - $exportExt = 'tsv'; - break; - case 'html' : - $exportExt = 'html'; - break; - case 'xml' : - $exportExt = 'xml'; - break; - default : - ZM\Fatal("Unrecognised log export format '$format'"); - } - $exportKey = substr(md5(rand()), 0, 8); - $exportFile = 'zm-log.'.$exportExt; - if ( ! ( mkdir(ZM_DIR_EXPORTS) || file_exists(ZM_DIR_EXPORTS) ) ) { - ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\''); - } - $exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt; - ZM\Logger::Debug("Exporting to $exportPath"); - if ( !($exportFP = fopen($exportPath, 'w')) ) - ZM\Fatal("Unable to open log export file $exportPath"); - $logs = array(); - foreach ( dbFetchAll($sql, NULL, $values) as $log ) { - $log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; - $logs[] = $log; - } - ZM\Logger::Debug(count($logs).' lines being exported by '.$sql.implode(',', $values)); - - switch( $format ) { - case 'text' : - { - foreach ( $logs as $log ) { - if ( $log['Line'] ) - fprintf($exportFP, "%s %s[%d].%s-%s/%d [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message']); - else - fprintf($exportFP, "%s %s[%d].%s-%s [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message']); - } - break; - } - case 'tsv' : - { - # This line doesn't need fprintf, it could use fwrite - fprintf($exportFP, join("\t", - translate('DateTime'), - translate('Component'), - translate('Server'), - translate('Pid'), - translate('Level'), - translate('Message'), - translate('File'), - translate('Line') - )."\n"); - foreach ( $logs as $log ) { - fprintf($exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", - $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']); - } - break; - } - case 'html' : - { - fwrite($exportFP, -' - - - '.translate('ZoneMinderLog').' - - - -

'.translate('ZoneMinderLog').'

-

'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'

-

'.count($logs).' '.translate('Logs').'

- - - - '); - foreach ( $logs as $log ) { - $classLevel = $log['Level']; - if ( $classLevel < ZM\Logger::FATAL ) { - $classLevel = ZM\Logger::FATAL; - } else if ( $classLevel > ZM\Logger::DEBUG ) { - $classLevel = ZM\Logger::DEBUG; - } - $logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]); - fprintf($exportFP, ' - ', $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']); - } - fwrite($exportFP, - ' -
'.translate('DateTime').''.translate('Component').''.translate('Server').''.translate('Pid').''.translate('Level').''.translate('Message').''.translate('File').''.translate('Line').'
%s%s%s%d%s%s%s%s
- - '); - break; - } - case 'xml' : - { - fwrite($exportFP, - ' - - '.$_POST['selector'].''); - foreach ( $filter as $field=>$value ) - if ( $value != '' ) - fwrite( $exportFP, - ' - <'.strtolower($field).'>'.htmlspecialchars($value).' - ' ); - fwrite( $exportFP, - ' - '.translate('DateTime').''.translate('Component').''.translate('Pid').''.translate('Level').''.translate('Message').''.translate('File').''.translate('Line').' - - - ' ); - foreach ( $logs as $log ) { - fprintf( $exportFP, - ' - %s - %s - %s - %d - %s - - %s - %d - -', $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] ); - } - fwrite( $exportFP, - ' - ' ); - break; - } - $exportExt = 'xml'; - break; - } - fclose( $exportFP ); - ajaxResponse( array( - 'key' => $exportKey, - 'format' => $format, - ) ); - break; - } - case 'download' : - { - if ( !canView('System') ) - ajaxError('Insufficient permissions to download logs'); - - if ( empty($_REQUEST['key']) ) - ZM\Fatal('No log export key given'); - $exportKey = $_REQUEST['key']; - if ( empty($_REQUEST['format']) ) - ZM\Fatal('No log export format given'); - $format = $_REQUEST['format']; - - switch ( $format ) { - case 'text' : - $exportExt = 'txt'; - break; - case 'tsv' : - $exportExt = 'tsv'; - break; - case 'html' : - $exportExt = 'html'; - break; - case 'xml' : - $exportExt = 'xml'; - break; - default : - ZM\Fatal("Unrecognised log export format '$format'"); - } - - $exportFile = 'zm-log.'.$exportExt; - $exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt; - - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false); // required by certain browsers - header('Content-Description: File Transfer'); - header('Content-Disposition: attachment; filename="'.$exportFile.'"'); - header('Content-Transfer-Encoding: binary'); - header('Content-Type: application/force-download'); - header('Content-Length: '.filesize($exportPath)); - readfile($exportPath); - exit(0); - break; - } -} // end switch ( $_REQUEST['task'] ) -ajaxError('Unrecognised action or insufficient permissions'); -?> + return $data; +} diff --git a/web/ajax/modal.php b/web/ajax/modal.php index bd96b137f..2794fb0af 100644 --- a/web/ajax/modal.php +++ b/web/ajax/modal.php @@ -8,7 +8,7 @@ if ( empty($_REQUEST['modal']) ) { $modal = validJsStr($_REQUEST['modal']); $data = array(); -ZM\Logger::Debug("Including modals/$modal.php"); +ZM\Debug("Including modals/$modal.php"); # Shouldn't be necessary but at the moment we have last .conf file contents ob_start(); @$result = include('modals/'.$modal.'.php'); diff --git a/web/ajax/modals/controlpreset.php b/web/ajax/modals/controlpreset.php new file mode 100644 index 000000000..e3cc02db3 --- /dev/null +++ b/web/ajax/modals/controlpreset.php @@ -0,0 +1,53 @@ + + diff --git a/web/ajax/modals/delconfirm.php b/web/ajax/modals/delconfirm.php index 9aec739f4..02f742cff 100644 --- a/web/ajax/modals/delconfirm.php +++ b/web/ajax/modals/delconfirm.php @@ -1,18 +1,20 @@ diff --git a/web/ajax/modals/download.php b/web/ajax/modals/download.php new file mode 100644 index 000000000..7009f8738 --- /dev/null +++ b/web/ajax/modals/download.php @@ -0,0 +1,119 @@ +'.PHP_EOL; + + $Event = new ZM\Event($eid); + if ( !$Event->Id() ) { + ZM\Error('Invalid event id'); + $result .= '
Invalid event id
'.PHP_EOL; + } else { + $result .= 'Downloading event ' . $Event->Id . '. Resulting file should be approximately ' . human_filesize( $Event->DiskSpace() ).PHP_EOL; + } + } else if ( !empty($eids) ) { + $total_size = 0; + foreach ( $eids as $eid ) { + if ( !validInt($eid) ) { + ZM\Warning("Invalid event id in eids[] $eid"); + continue; + } + $Event = new ZM\Event($eid); + $total_size += $Event->DiskSpace(); + $result .= ''.PHP_EOL; + } + unset($eid); + $result .= 'Downloading ' . count($eids) . ' events. Resulting file should be approximately ' . human_filesize($total_size).PHP_EOL; + } else { + $result .= '
There are no events found. Resulting download will be empty.
'; + } + + return $result; +} + +if ( !canView('Events') ) { + $view = 'error'; + return; +} + +$eid = isset($_REQUEST['eid']) ? $_REQUEST['eid'] : ''; +$eids = isset($_REQUEST['eids']) ? $_REQUEST['eids'] : array(); +$generated = isset($_REQUEST['generated']) ? $_REQUEST['generated'] : ''; + +$total_size = 0; +if ( isset($_SESSION['montageReviewFilter']) and !$eids ) { + # Handles montageReview filter + $eventsSql = 'SELECT E.Id, E.DiskSpace FROM Events AS E WHERE 1'; + $eventsSql .= $_SESSION['montageReviewFilter']['sql']; + $results = dbQuery($eventsSql); + while ( $event_row = dbFetchNext( $results ) ) { + array_push($eids, $event_row['Id']); + $total_size += $event_row['DiskSpace']; + } + if ( ! count($eids) ) { + ZM\Error("No events found for download using $eventsSql"); + } + #session_start(); + #unset($_SESSION['montageReviewFilter']); + #session_write_close(); +#} else { +#Debug("NO montageReviewFilter"); +} + +$exportFormat = ''; +if ( isset($_REQUEST['exportFormat']) ) { + if ( !in_array($_REQUEST['exportFormat'], array('zip', 'tar')) ) { + ZM\Error('Invalid exportFormat: '.$_REQUEST['exportFormat']); + } else { + $exportFormat = $_REQUEST['exportFormat']; + } +} + +$focusWindow = true; +$connkey = isset($_REQUEST['connkey']) ? validInt($_REQUEST['connkey']) : generateConnKey(); + +?> +
diff --git a/web/ajax/modals/eventrename.php b/web/ajax/modals/eventrename.php new file mode 100644 index 000000000..c877d57b4 --- /dev/null +++ b/web/ajax/modals/eventrename.php @@ -0,0 +1,27 @@ + + diff --git a/web/skins/classic/views/function.php b/web/ajax/modals/function.php similarity index 54% rename from web/skins/classic/views/function.php rename to web/ajax/modals/function.php index 83d67d54a..08b470840 100644 --- a/web/skins/classic/views/function.php +++ b/web/ajax/modals/function.php @@ -18,17 +18,18 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canEdit('Monitors') ) { - $view = 'error'; - return; -} -?> +if ( !canEdit('Monitors') ) return; +?> diff --git a/web/ajax/modals/group.php b/web/ajax/modals/group.php new file mode 100644 index 000000000..119149951 --- /dev/null +++ b/web/ajax/modals/group.php @@ -0,0 +1,135 @@ +Id(); +} + +function get_children($Group) { + global $children; + + $kids = array(); + if ( isset( $children[$Group->Id()] ) ) { + $kids += array_map('get_Id', $children[$Group->Id()]); + foreach ( $children[$Group->Id()] as $G ) { + foreach ( get_children($G) as $id ) { + $kids[] = $id; + } + } + } + return $kids; +} + +function parentGrpSelect($newGroup) { + $Groups = array(); + foreach ( ZM\Group::find() as $Group ) { + $Groups[$Group->Id()] = $Group; + } + + # This array is indexed by parent_id + $children = array(); + + foreach ( $Groups as $id=>$Group ) { + if ( $Group->ParentId() != null ) { + if ( ! isset( $children[$Group->ParentId()] ) ) + $children[$Group->ParentId()] = array(); + $children[$Group->ParentId()][] = $Group; + } + } + + $kids = get_children($newGroup); + if ( $newGroup->Id() ) + $kids[] = $newGroup->Id(); + $sql = 'SELECT Id,Name FROM `Groups`'.(count($kids)?' WHERE Id NOT IN ('.implode(',',array_map(function(){return '?';}, $kids)).')' : '').' ORDER BY Name'; + $options = array(''=>'None'); + + foreach ( dbFetchAll($sql, null, $kids) as $option ) { + $options[$option['Id']] = str_repeat('  ', $Groups[$option['Id']]->depth()) . $option['Name']; + } + + return htmlSelect('newGroup[ParentId]', $options, $newGroup->ParentId(), array('data-on-change'=>'configModalBtns')); +} + +function monitorList($newGroup) { + $result = ''; + + $monitors = dbFetchAll('SELECT Id,Name FROM Monitors ORDER BY Sequence ASC'); + $monitorIds = $newGroup->MonitorIds(); + foreach ( $monitors as $monitor ) { + if ( visibleMonitor($monitor['Id']) ) { + $result .= ''.PHP_EOL; + } + } + + return $result; +} + +// +// INITIAL SANITY CHECKS +// + +if ( !canEdit('Groups') ) { + $view = 'error'; + return; +} + +if ( !empty($_REQUEST['gid']) ) { + $newGroup = new ZM\Group($_REQUEST['gid']); +} else { + $newGroup = new ZM\Group(); +} + +// +// BEGIN HTML +// +?> + diff --git a/web/ajax/modals/log_export.php b/web/ajax/modals/log_export.php new file mode 100644 index 000000000..8f359bb86 --- /dev/null +++ b/web/ajax/modals/log_export.php @@ -0,0 +1,46 @@ +