diff --git a/.cirrus.yml b/.cirrus.yml index 117aadf78..7b1effe31 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,22 +1,28 @@ task: name: freebsd-build freebsd_instance: - image_family: freebsd-12-2 + matrix: + - image_family: freebsd-12-2 + - image_family: freebsd-13-0 prepare_script: - - pkg install -yq git cmake jpeg-turbo mysql80-client ffmpeg libvncserver libjwt libfmt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap - + - pkg install -yq git cmake pkgconf jpeg-turbo mysql80-client ffmpeg libvncserver libjwt libfmt catch p5-DBI p5-DBD-mysql p5-Date-Manip p5-Test-LWP-UserAgent p5-Sys-Mmap v4l_compat + configure_script: - git submodule update --init --recursive - mkdir build - cd build - - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 + - cmake --version + - cmake ../ -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DCMAKE_C_FLAGS="-Wno-deprecated-declarations" -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" build_script: - cd build - make -j3 + + install_script: + - cd build - make install test_script: - - cd build - - make test + - cd build/tests + - ./tests "~[notCI]" diff --git a/.eslintignore b/.eslintignore index 4682851df..91b0fd196 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,7 @@ web/api/lib web/includes/csrf/ web/js/videojs.zoomrotate.js -web/skins/classic/js/bootstrap.js +web/skins/classic/js/bootstrap-4.5.0.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8804e639c..5994f0e6b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,7 +2,7 @@ github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: zoneminder # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username +open_collective: zoneminder # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry diff --git a/.github/workflows/ci-bionic.yml b/.github/workflows/ci-bionic.yml new file mode 100644 index 000000000..dda5c5cd3 --- /dev/null +++ b/.github/workflows/ci-bionic.yml @@ -0,0 +1,51 @@ +name: CI Ubuntu Bionic (18.04) + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + runs-on: ubuntu-latest + container: ubuntu:bionic + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade && apt-get -qq install software-properties-common + - name: Install git + run: | + add-apt-repository ppa:git-core/ppa + apt-get -qq update + apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-bullseye.yml b/.github/workflows/ci-bullseye.yml new file mode 100644 index 000000000..7d1882844 --- /dev/null +++ b/.github/workflows/ci-bullseye.yml @@ -0,0 +1,57 @@ +name: CI Debian Bullseye + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: gnutls + crypto_package: libgnutls28-dev + jwt_package: libjwt-gnutls-dev + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + runs-on: ubuntu-latest + container: debian:bullseye + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + catch2 + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) + - name: Run tests + run: cd build/tests && ./tests "~[notCI]" diff --git a/.github/workflows/ci-buster.yml b/.github/workflows/ci-buster.yml new file mode 100644 index 000000000..7411366dd --- /dev/null +++ b/.github/workflows/ci-buster.yml @@ -0,0 +1,54 @@ +name: CI Debian Buster + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + include: + - crypto_backend: gnutls + crypto_package: libgnutls28-dev + jwt_package: libjwt-gnutls-dev + - crypto_backend: openssl + crypto_package: libssl-dev + jwt_package: libjwt-dev + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + runs-on: ubuntu-latest + container: debian:buster + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + ${{ matrix.crypto_package }} ${{ matrix.jwt_package }} + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-centos-7.yml b/.github/workflows/ci-centos-7.yml new file mode 100644 index 000000000..986b7815b --- /dev/null +++ b/.github/workflows/ci-centos-7.yml @@ -0,0 +1,39 @@ +name: CI CentOS 7 + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + - crypto_backend: gnutls + jwt_backend: libjwt + runs-on: ubuntu-latest + container: centos:7 + + steps: + - name: Enable RPMFusion and EPEL + run: yum -y install https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + - name: Install git + run: yum -y install https://repo.ius.io/ius-release-el7.rpm && yum -y install git224 + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: yum -y update && yum -y install make cmake3 gcc-c++ mariadb-devel ffmpeg-devel libcurl-devel vlc-devel libvncserver-devel libjpeg-turbo-devel "perl(Date::Manip)" "perl(DBD::mysql)" "perl(ExtUtils::MakeMaker)" "perl(Sys::Mmap)" "perl(Sys::Syslog)" "perl(LWP::UserAgent)" polkit-devel libjwt-devel + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake3 --version && cmake3 .. -DBUILD_MAN=0 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) diff --git a/.github/workflows/ci-centos-8.yml b/.github/workflows/ci-centos-8.yml new file mode 100644 index 000000000..f01f9929b --- /dev/null +++ b/.github/workflows/ci-centos-8.yml @@ -0,0 +1,41 @@ +name: CI CentOS 8 + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + crypto_backend: [ gnutls, openssl ] + jwt_backend: [ libjwt, jwt_cpp ] + exclude: + - crypto_backend: gnutls + jwt_backend: jwt_cpp + - crypto_backend: gnutls + jwt_backend: libjwt + runs-on: ubuntu-latest + container: centos:8 + + steps: + - name: Enable RPMFusion, EPEL and PowerTools + run: yum -y install "dnf-command(config-manager)" https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && yum config-manager --set-enabled powertools + - name: Install git + run: yum -y install git + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: yum -y update && yum -y install make cmake gcc-c++ catch-devel mariadb-devel ffmpeg-devel libcurl-devel vlc-devel libvncserver-devel libjpeg-turbo-devel "perl(Date::Manip)" "perl(DBD::mysql)" "perl(ExtUtils::MakeMaker)" "perl(Sys::Mmap)" "perl(Sys::Syslog)" "perl(LWP::UserAgent)" polkit-devel libjwt-devel + - name: Prepare + run: mkdir build + - name: Configure + run: cd build && cmake --version && cmake .. -DBUILD_MAN=0 -DBUILD_TEST_SUITE=1 -DENABLE_WERROR=1 -DZM_CRYPTO_BACKEND=${{ matrix.crypto_backend }} -DZM_JWT_BACKEND=${{ matrix.jwt_backend }} + - name: Build + run: cd build && make -j3 | grep --line-buffered -Ev '^(cp lib\/|Installing.+\.pm)' && (exit ${PIPESTATUS[0]}) + - name: Run tests + run: cd build/tests && ./tests "~[notCI]" diff --git a/.github/workflows/ci-eslint.yml b/.github/workflows/ci-eslint.yml new file mode 100644 index 000000000..7a5c4337f --- /dev/null +++ b/.github/workflows/ci-eslint.yml @@ -0,0 +1,21 @@ +name: CI ESLint + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + eslint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install ESLint + run: npm install eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5 + - name: Run ESLint + run: npx eslint --ext .php,.js . diff --git a/.github/workflows/ci-stretch.yml b/.github/workflows/ci-stretch.yml new file mode 100644 index 000000000..e42d4db3d --- /dev/null +++ b/.github/workflows/ci-stretch.yml @@ -0,0 +1,40 @@ +name: CI Debian Stretch + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + defaults: + run: + shell: bash + runs-on: ubuntu-latest + container: debian:stretch-backports + + steps: + - name: Update packages + run: apt-get -qq update && apt-get -qq upgrade + - name: Install git + run: apt-get -qq install git/stretch-backports git-man/stretch-backports + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: > + apt-get -qq install make cmake g++ + default-libmysqlclient-dev + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev + libcurl4-gnutls-dev libvlc-dev libvncserver-dev + libdate-manip-perl libdbd-mysql-perl libsys-mmap-perl libwww-perl + libpolkit-gobject-1-dev + libssl-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 index 1efdaf96b..80b4f9bf2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -52,9 +52,11 @@ jobs: git submodule init git submodule update --init --recursive sudo apt-get update - sudo apt-get install libx264-dev libmp4v2-dev libavdevice-dev libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-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 libfmt-dev + + sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libjwt-gnutls-dev + sudo apt-get install libbz2-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 ab9c538c3 + 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 diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml new file mode 100644 index 000000000..6a76da322 --- /dev/null +++ b/.github/workflows/create-packages.yml @@ -0,0 +1,31 @@ +name: Create packages + +on: + push: + branches: + - '*' + pull_request: + branches: [ master ] + +jobs: + package: + strategy: + matrix: + os_dist: + - os: debian + dist: buster + - os: debian + dist: bullseye + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Run packpack + env: + SMPFLAGS: -j4 + OS: ${{ matrix.os_dist.os }} + DIST: ${{ matrix.os_dist.dist }} + DOCKER_REPO: iconzm/packpack + run: utils/packpack/startpackpack.sh diff --git a/.gitignore b/.gitignore index e7ae9ceab..3d61b4f54 100644 --- a/.gitignore +++ b/.gitignore @@ -120,12 +120,11 @@ src/CMakeFiles/ src/cmake_install.cmake src/libzm.a src/nph-zms -src/zm_config_data.h -src/zm_config_defines.h src/zmc src/zmf src/zms src/zmu +src/zm_rtsp_server src/zoneminder-zmc.8 src/zoneminder-zmc.8.gz src/zoneminder-zmf.8 @@ -154,4 +153,6 @@ web/undef.log zm.conf zmconfgen.pl zmlinkcontent.sh +zm_config_data.h +zm_config_defines.h **/.DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..ab8411b23 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,18 @@ +default: + image: + name: ubuntu:latest + before_script: + - apt-get update -yq + - DEBIAN_FRONTEND=noninteractive apt-get install -yq devscripts sudo + +deb: + stage: build + tags: + - docker + script: + - yes "" | ./utils/do_debian_package.sh --snapshot=stable --type=binary --interactive=no --dput=no --debbuild-extra=--no-sign || true + timeout: 2h + artifacts: + paths: + - '*.deb' + expire_in: 1 week diff --git a/.gitmodules b/.gitmodules index 9f21bb62b..fd07ba226 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,4 +4,7 @@ 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 deleted file mode 100644 index c370e9318..000000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: cpp -sudo: required -dist: bionic -git: - depth: 9999999 -notifications: - irc: chat.freenode.net#zoneminder-dev -branches: - except: - - modern -cache: ccache -addons: - ssh_known_hosts: zmrepo.zoneminder.com - apt: - sources: - - sourceline: ppa:iconnor/zoneminder-master - - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 - packages: - - gdebi - - yum-utils - - patch - - git - - curl - - sshfs - - sed - - binfmt-support - - qemu - - qemu-user-static - - dnsutils - - traceroute -install: - - update-binfmts --enable qemu-arm - -env: - - SMPFLAGS=-j4 OS=eslint DIST=eslint - - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack - -compiler: -- gcc -services: -- mysql -- docker - -script: -- utils/packpack/startpackpack.sh - -before_deploy: -- openssl aes-256-cbc -K $encrypted_62a62750aa73_key -iv $encrypted_62a62750aa73_iv -in ./utils/packpack/deploy_rsa.enc -out /tmp/deploy_rsa -d -- eval "$(ssh-agent -s)" -- chmod 600 /tmp/deploy_rsa -- ssh-add /tmp/deploy_rsa - -deploy: - provider: script - skip_cleanup: true - script: utils/packpack/rsync_xfer.sh - on: - branch: master diff --git a/CMakeLists.txt b/CMakeLists.txt index c3fc4ae8f..ca9c7384b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,9 +69,7 @@ include(GNUInstallDirs) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckFunctionExists) -include(CheckPrototypeDefinition_fixed) include(CheckTypeSize) -include(CheckStructHasMember) include(CheckSendfile) # Configuration options @@ -90,6 +88,7 @@ mark_as_advanced( ZM_SYSTEMD ZM_MANPAGE_DEST_PREFIX) +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) @@ -169,7 +168,7 @@ 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_NO_RTSPSERVER "OFF" CACHE BOOL - "Set to ON to skip live555 checks and force building ZM without rtsp server support. default: OFF") + "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 @@ -188,8 +187,32 @@ 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_CAKEPHP_CACHE "Apc" CACHE STRING + "Set the CakePHP cache engine, default: Apc") + +# Supported crypto backends. Using OpenSSL by default to be compatible with jwt-cpp. +set(ZM_CRYPTO_BACKEND_OPTIONS gnutls openssl) +set(ZM_CRYPTO_BACKEND openssl CACHE STRING "Determines which crypto backend should be used.") +set_property(CACHE ZM_CRYPTO_BACKEND PROPERTY STRINGS ${ZM_CRYPTO_BACKEND_OPTIONS}) + +if(NOT ZM_CRYPTO_BACKEND IN_LIST ZM_CRYPTO_BACKEND_OPTIONS) + message(FATAL_ERROR "Invalid value for ZM_CRYPTO_BACKEND. Possible options: ${ZM_CRYPTO_BACKEND_OPTIONS}") +endif() + +# Supported JWT backends. Using jwt-cpp as default. +set(ZM_JWT_BACKEND_OPTIONS libjwt jwt_cpp) +set(ZM_JWT_BACKEND jwt_cpp CACHE STRING "Determines which JWT backend should be used.") +set_property(CACHE ZM_JWT_BACKEND PROPERTY STRINGS ${ZM_JWT_BACKEND_OPTIONS}) + +if(NOT ZM_JWT_BACKEND IN_LIST ZM_JWT_BACKEND_OPTIONS) + message(FATAL_ERROR "Invalid value for ZM_JWT_BACKEND. Possible options: ${ZM_JWT_BACKEND_OPTIONS}") +endif() # Reassign some variables if a target distro has been specified +if(ZM_TARGET_DISTRO MATCHES "^fc") + set(ZM_CAKEPHP_CACHE "Memcached") +endif() + 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") @@ -236,8 +259,6 @@ 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}") # This is required to enable searching in lib64 (if exists), do not change set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) @@ -247,11 +268,6 @@ if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/sy 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() -check_include_file("linux/videodev2.h" HAVE_LINUX_VIDEODEV2_H) check_include_file("execinfo.h" HAVE_EXECINFO_H) if(HAVE_EXECINFO_H) check_function_exists("backtrace" HAVE_DECL_BACKTRACE) @@ -331,19 +347,20 @@ else() "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() - set(optlibsnotfound "${optlibsnotfound} LIBJWT") +# libjwt +if (${ZM_JWT_BACKEND} STREQUAL "libjwt") + find_package(LibJWT REQUIRED COMPONENTS ${ZM_CRYPTO_BACKEND}) + if(LIBJWT_FOUND) + set(HAVE_LIBJWT 1) + set(optlibsfound "${optlibsfound} LIBJWT") + else() + set(optlibsnotfound "${optlibsnotfound} LIBJWT") + endif() endif() -# gnutls (using find_library and find_path) -if(HAVE_LIBJWT) - find_library(GNUTLS_LIBRARIES gnutls) +# GnuTLS +if (${ZM_CRYPTO_BACKEND} STREQUAL "gnutls") + find_library(GNUTLS_LIBRARIES gnutls REQUIRED) if(GNUTLS_LIBRARIES) set(HAVE_LIBGNUTLS 1) list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") @@ -353,23 +370,18 @@ if(HAVE_LIBJWT) 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) +elseif (${ZM_CRYPTO_BACKEND} STREQUAL "openssl") + find_package(OpenSSL REQUIRED) 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") @@ -412,23 +424,6 @@ 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() - 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) @@ -448,129 +443,19 @@ else() message(FATAL_ERROR "ZoneMinder requires mysqlclient but it was not found on your system") endif() +find_package(FFMPEG 55.34.100 REQUIRED + COMPONENTS + avcodec + avformat + avutil + swresample + swscale) + +set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_avutil_INCLUDE_DIRS}) +check_include_file("libavutil/hwcontext.h" HAVE_LIBAVUTIL_HWCONTEXT_H) + 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() - 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() - 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() - -# 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() - 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() - 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() - 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) -if(SWRESAMPLE_LIBRARIES) - set(HAVE_LIBSWRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}") - find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg) - if(SWRESAMPLE_INCLUDE_DIR) - include_directories("${SWRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${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() - set(optlibsnotfound "${optlibsnotfound} SWResample") - - # AVresample (using find_library and find_path) - find_library(AVRESAMPLE_LIBRARIES avresample) - if(AVRESAMPLE_LIBRARIES) - set(HAVE_LIBAVRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") - find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) - if(AVRESAMPLE_INCLUDE_DIR) - include_directories("${AVRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${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() - set(optlibsnotfound "${optlibsnotfound} AVResample") - endif() -endif() # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE @@ -629,17 +514,7 @@ endif() #endif() if(NOT ZM_NO_RTSPSERVER) - find_package(Live555) - if(Live555_FOUND) - include_directories(${Live555_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_INCLUDES "${Live555_INCLUDE_DIRS}") - list(APPEND ZM_BIN_LIBS "${Live555_LIBRARIES}") - set(HAVE_RTSP_SERVER 1) - set(optlibsfound "${optlibsfound} libLive555") - else() - set(HAVE_RTSP_SERVER 0) - set(optlibsnotfound "${optlibsnotfound} libLive555") - endif() + set(HAVE_RTSP_SERVER 1) else() set(HAVE_RTSP_SERVER 0) endif() @@ -649,29 +524,19 @@ find_package(fmt REQUIRED) # # *** 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") +# If libjwt is not present we fall back to jwt-cpp which requires OpenSSL +if((NOT HAVE_LIBJWT) AND (NOT HAVE_LIBOPENSSL)) + message(FATAL_ERROR "Using the jwt-cpp backend requires OpenSSL as crypto backend.") 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 -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() -if(HAVE_LINUX_VIDEODEV2_H) - set(ZM_HAS_V4L 1) +find_package(V4L2) +if(TARGET V4L2::videodev2) set(ZM_HAS_V4L2 1) -endif() -if((NOT HAVE_LINUX_VIDEODEV_H) - AND (NOT HAVE_LIBV4L1_VIDEODEV_H) - AND (NOT HAVE_LINUX_VIDEODEV2_H)) +else() + set(ZM_HAS_V4L2 0) 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) @@ -692,42 +557,6 @@ if(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() - -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() - -if(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() - -# 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() - # Check for Perl find_package(Perl) if(NOT PERL_FOUND) @@ -865,6 +694,9 @@ endif() message(STATUS "Optional libraries found:${optlibsfound}") message(STATUS "Optional libraries not found:${optlibsnotfound}") +message(STATUS "Enabled crypto backend: ${ZM_CRYPTO_BACKEND}") +message(STATUS "Enabled JWT backend: ${ZM_JWT_BACKEND}") + # Run ZM configuration generator message(STATUS "Running ZoneMinder configuration generator") execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE ZMCONFGEN_RESULT) diff --git a/README.md b/README.md index 20cce499c..2249f1752 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org) -- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) +- OpenSuse via [third party repository](https://wiki.zoneminder.com/Installing_using_ZoneMinder_RPMs_for_SuSE) - Mageia from their default repository - Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) - Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder) diff --git a/cmake/Modules/CheckPrototypeDefinition.c.in b/cmake/Modules/CheckPrototypeDefinition.c.in deleted file mode 100644 index a97344ac3..000000000 --- a/cmake/Modules/CheckPrototypeDefinition.c.in +++ /dev/null @@ -1,29 +0,0 @@ -@CHECK_PROTOTYPE_DEFINITION_HEADER@ - -static void cmakeRequireSymbol(int dummy, ...) { - (void) dummy; -} - -static void checkSymbol(void) { -#ifndef @CHECK_PROTOTYPE_DEFINITION_SYMBOL@ - cmakeRequireSymbol(0, &@CHECK_PROTOTYPE_DEFINITION_SYMBOL@); -#endif -} - -@CHECK_PROTOTYPE_DEFINITION_PROTO@ { - return @CHECK_PROTOTYPE_DEFINITION_RETURN@; -} - -#ifdef __CLASSIC_C__ -int main() { - int ac; - char*av[]; -#else -int main(int ac, char *av[]) { -#endif - checkSymbol(); - if (ac > 1000) { - return *av[0]; - } - return 0; -} diff --git a/cmake/Modules/CheckPrototypeDefinition.cmake b/cmake/Modules/CheckPrototypeDefinition.cmake deleted file mode 100644 index 2342b3c4f..000000000 --- a/cmake/Modules/CheckPrototypeDefinition.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# - Check if the protoype we expect is correct. -# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) -# FUNCTION - The name of the function (used to check if prototype exists) -# PROTOTYPE- The prototype to check. -# RETURN - The return value of the function. -# HEADER - The header files required. -# VARIABLE - The variable to store the result. -# Example: -# check_prototype_definition(getpwent_r -# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" -# "NULL" -# "unistd.h;pwd.h" -# SOLARIS_GETPWENT_R) -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# CMAKE_REQUIRED_LIBRARIES = list of libraries to link - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# Copyright 2010-2011 Andreas Schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) -# - - -get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) - - -function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) - - if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") - set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") - - set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) - if (CMAKE_REQUIRED_LIBRARIES) - set(CHECK_PROTOTYPE_DEFINITION_LIBS - LINK_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - else() - set(CHECK_PROTOTYPE_DEFINITION_LIBS) - endif() - if (CMAKE_REQUIRED_INCLUDES) - set(CMAKE_SYMBOL_EXISTS_INCLUDES - "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - else() - set(CMAKE_SYMBOL_EXISTS_INCLUDES) - endif() - - foreach(_FILE ${_HEADER}) - set(CHECK_PROTOTYPE_DEFINITION_HEADER - "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") - endforeach() - - set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) - set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) - set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) - - configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" - "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) - - file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) - - try_compile(${_VARIABLE} - ${CMAKE_BINARY_DIR} - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - ${CHECK_PROTOTYPE_DEFINITION_LIBS} - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} - "${CMAKE_SYMBOL_EXISTS_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - - if (${_VARIABLE}) - set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" - "${OUTPUT}\n\n") - else () - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") - set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" - "${OUTPUT}\n\n${_SOURCE}\n\n") - endif () - endif() - -endfunction() diff --git a/cmake/Modules/CheckPrototypeDefinition_fixed.cmake b/cmake/Modules/CheckPrototypeDefinition_fixed.cmake deleted file mode 100644 index 550bcaa50..000000000 --- a/cmake/Modules/CheckPrototypeDefinition_fixed.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# - Check if the protoype we expect is correct. -# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) -# FUNCTION - The name of the function (used to check if prototype exists) -# PROTOTYPE- The prototype to check. -# RETURN - The return value of the function. -# HEADER - The header files required. -# VARIABLE - The variable to store the result. -# Example: -# check_prototype_definition(getpwent_r -# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" -# "NULL" -# "unistd.h;pwd.h" -# SOLARIS_GETPWENT_R) -# The following variables may be set before calling this macro to -# modify the way the check is run: -# -# CMAKE_REQUIRED_FLAGS = string of compile command line flags -# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) -# CMAKE_REQUIRED_INCLUDES = list of include directories -# CMAKE_REQUIRED_LIBRARIES = list of libraries to link - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# Copyright 2010-2011 Andreas Schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) -# - - -get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) - - -function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) - - if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") - set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") - - set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) - if (CMAKE_REQUIRED_LIBRARIES) - set(CHECK_PROTOTYPE_DEFINITION_LIBS - ${LINK_LIBRARIES} ${CMAKE_REQUIRED_LIBRARIES}) - else() - set(CHECK_PROTOTYPE_DEFINITION_LIBS) - endif() - if (CMAKE_REQUIRED_INCLUDES) - set(CMAKE_SYMBOL_EXISTS_INCLUDES - "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") - else() - set(CMAKE_SYMBOL_EXISTS_INCLUDES) - endif() - - foreach(_FILE ${_HEADER}) - set(CHECK_PROTOTYPE_DEFINITION_HEADER - "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") - endforeach() - - set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) - set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) - set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) - - configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" - "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) - - file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) - - try_compile(${_VARIABLE} - ${CMAKE_BINARY_DIR} - ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c - COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - ${CHECK_PROTOTYPE_DEFINITION_LIBS} - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} - "${CMAKE_SYMBOL_EXISTS_INCLUDES}" - OUTPUT_VARIABLE OUTPUT) - - if (${_VARIABLE}) - set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" - "${OUTPUT}\n\n") - else () - message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") - set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") - file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" - "${OUTPUT}\n\n${_SOURCE}\n\n") - endif () - endif() - -endfunction() diff --git a/cmake/Modules/FindFFMPEG.cmake b/cmake/Modules/FindFFMPEG.cmake new file mode 100644 index 000000000..9048834c0 --- /dev/null +++ b/cmake/Modules/FindFFMPEG.cmake @@ -0,0 +1,163 @@ +#[=======================================================================[.rst: +FindFFMPEG +---------- + +Find the FFmpeg and associated libraries. + + +This module accepts following COMPONENTS:: + + avcodec + avdevice + avfilter + avformat + avutil + swresample + swscale + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``FFMPEG::`` + The FFmpeg component. + +Result Variables +^^^^^^^^^^^^^^^^ + +``FFMPEG_INCLUDE_DIRS`` + Include directories necessary to use FFmpeg. +``FFMPEG_LIBRARIES`` + Libraries necessary to use FFmpeg. Note that this only includes libraries for the components requested. +``FFMPEG_VERSION`` + The version of FFMPEG found (avutil). + + +For each component, the following are provided: + +``FFMPEG__FOUND`` + FFmpeg component was found. +``FFMPEG__INCLUDE_DIRS`` + Include directories for the component. +``FFMPEG__LIBRARIES`` + Libraries for the component. + +#]=======================================================================] + +function(_ffmpeg_find component pkgconfig_name header) + find_package(PkgConfig) + pkg_check_modules(PC_FFMPEG_${component} ${pkgconfig_name}) + + find_path(FFMPEG_${component}_INCLUDE_DIR + NAMES "lib${component}/${header}" + HINTS + ${PC_FFMPEG_${component}_INCLUDEDIR} + ${PC_FFMPEG_${component}_INCLUDE_DIRS} + PATH_SUFFIXES + ffmpeg) + mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR") + + find_library(FFMPEG_${component}_LIBRARY + NAMES + ${component} + ${PC_FFMPEG_${component}_LIBRARIES} + HINTS + ${PC_FFMPEG_${component}_LIBDIR} + ${PC_FFMPEG_${component}_LIBRARY_DIRS}) + mark_as_advanced("${component}_LIBRARY") + + if(FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR) + set(_deps_found TRUE) + set(_deps_link) + foreach(_ffmpeg_dep IN LISTS ARGN) + if(TARGET "FFMPEG::${_ffmpeg_dep}") + list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}") + else() + set(_deps_found FALSE) + endif() + endforeach() + if(_deps_found) + if(NOT TARGET "FFMPEG::${component}") + add_library("FFMPEG::${component}" UNKNOWN IMPORTED) + set_target_properties("FFMPEG::${component}" PROPERTIES + IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}") + endif() + set(FFMPEG_${component}_FOUND 1 PARENT_SCOPE) + set(FFMPEG_${component}_VERSION "${PC_FFMPEG_${component}_VERSION}" PARENT_SCOPE) + else() + set("FFMPEG_${component}_FOUND" 0 PARENT_SCOPE) + set(what) + if(NOT FFMPEG_${component}_LIBRARY) + set(what "library") + endif() + if(NOT FFMPEG_${component}_INCLUDE_DIR) + if(what) + string(APPEND what " or headers") + else() + set(what "headers") + endif() + endif() + set("FFMPEG_${component}_NOT_FOUND_MESSAGE" + "Could not find the ${what} for ${component}." + PARENT_SCOPE) + endif() + endif() +endfunction() + +_ffmpeg_find(avutil libavutil avutil.h) +_ffmpeg_find(swresample libswresample swresample.h + avutil) +_ffmpeg_find(swscale libswscale swscale.h + avutil) +_ffmpeg_find(avcodec libavcodec avcodec.h + avutil) +_ffmpeg_find(avformat libavformat avformat.h + avcodec avutil) +_ffmpeg_find(avfilter libavfilter avfilter.h + avutil) +_ffmpeg_find(avdevice libavdevice avdevice.h + avformat avutil) + +if(TARGET FFMPEG::avutil) + set(FFMPEG_VERSION "${FFMPEG_avutil_VERSION}") +endif() + +set(FFMPEG_INCLUDE_DIRS) +set(FFMPEG_LIBRARIES) +set(_ffmpeg_required_vars) +foreach(_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS) + if(TARGET "FFMPEG::${_ffmpeg_component}") + set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}") + set(FFMPEG_${_ffmpeg_component}_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARY}") + list(APPEND FFMPEG_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}") + list(APPEND FFMPEG_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARIES}") + if(FFMEG_FIND_REQUIRED_${_ffmpeg_component}) + list(APPEND _ffmpeg_required_vars + "FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS" + "FFMPEG_${_ffmpeg_required_vars}_LIBRARIES") + endif() + endif() +endforeach() +unset(_ffmpeg_component) + +if(FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFMPEG + REQUIRED_VARS + FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + ${_ffmpeg_required_vars} + VERSION_VAR + FFMPEG_VERSION + HANDLE_COMPONENTS) +unset(_ffmpeg_required_vars) diff --git a/cmake/Modules/FindLibJWT.cmake b/cmake/Modules/FindLibJWT.cmake index e0c834609..c82065f9d 100644 --- a/cmake/Modules/FindLibJWT.cmake +++ b/cmake/Modules/FindLibJWT.cmake @@ -1,28 +1,89 @@ -include(FindPackageHandleStandardArgs) +#[=======================================================================[.rst: +FindLibJWT +---------- +Find the JWT C Library (libjwt) + + +This module accepts optional COMPONENTS to select the crypto backend (these are mutually exclusive):: + + openssl (default) + gnutls + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``JWT::libjwt`` + The JWT library, if found with the specified crypto backend. + +Result Variables +^^^^^^^^^^^^^^^^ + +``LIBJWT_FOUND`` + System has libjwt +``LIBJWT_INCLUDE_DIRS`` + The libjwt include directory +``LIBJWT_LIBRARIES`` + The libraries needed to use libjwt +#]=======================================================================] + +include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) + +if(LibJWT_FIND_COMPONENTS) + set(LIBJWT_CRYPTO_BACKEND "") + foreach(component IN LISTS LibJWT_FIND_COMPONENTS) + if(component MATCHES "^(openssl|gnutls)") + if(LIBJWT_CRYPTO_BACKEND) + message(FATAL_ERROR "LibJWT: Only one crypto library can be selected.") + endif() + set(LIBJWT_CRYPTO_BACKEND ${component}) + else() + message(FATAL_ERROR "LibJWT: Wrong crypto backend specified.") + endif() + endforeach() +else() + set(LIBJWT_CRYPTO_BACKEND "openssl") +endif() + +set(LIBJWT_LIB_NAMES "") +if(LIBJWT_CRYPTO_BACKEND STREQUAL "openssl") + set(LIBJWT_LIB_NAMES "jwt" "libjwt") +elseif(LIBJWT_CRYPTO_BACKEND STREQUAL "gnutls") + set(LIBJWT_LIB_NAMES "jwt-gnutls" "libjwt-gnutls") +endif() + pkg_check_modules(PC_LIBJWT QUIET libjwt) find_path(LIBJWT_INCLUDE_DIR NAMES jwt.h - HINTS ${PC_LIBJWT_INCLUDEDIR} ${PC_LIBJWT_INCLUDE_DIRS} - ) + HINTS + ${PC_LIBJWT_INCLUDEDIR} + ${PC_LIBJWT_INCLUDE_DIRS}) +mark_as_advanced(LIBJWT_INCLUDE_DIR) find_library(LIBJWT_LIBRARY - NAMES jwt-gnutls libjwt-gnutls liblibjwt-gnutls - HINTS ${PC_LIBJWT_LIBDIR} ${PC_LIBJWT_LIBRARY_DIR} - ) + NAMES ${LIBJWT_LIB_NAMES} + HINTS + ${PC_LIBJWT_LIBDIR} + ${PC_LIBJWT_LIBRARY_DIR}) +mark_as_advanced(LIBJWT_LIBRARY) find_package_handle_standard_args(LibJWT - REQUIRED_VARS LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY - ) + REQUIRED_VARS + LIBJWT_INCLUDE_DIR + LIBJWT_LIBRARY + FAIL_MESSAGE + "Could NOT find LibJWT with the crypto backend ${LIBJWT_CRYPTO_BACKEND}.") if(LIBJWT_FOUND) - add_library(libjwt STATIC IMPORTED GLOBAL) - set_target_properties(libjwt PROPERTIES - IMPORTED_LOCATION "${LIBJWT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBJWT_INCLUDE_DIR}" - ) -endif() + set(LIBJWT_LIBRARIES ${LIBJWT_LIBRARY}) + set(LIBJWT_INCLUDE_DIRS ${LIBJWT_INCLUDE_DIR}) -mark_as_advanced(LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY) \ No newline at end of file + add_library(JWT::libjwt UNKNOWN IMPORTED) + set_target_properties(JWT::libjwt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LIBJWT_INCLUDE_DIRS}" + IMPORTED_LOCATION "${LIBJWT_LIBRARY}") +endif() diff --git a/cmake/Modules/FindLive555.cmake b/cmake/Modules/FindLive555.cmake deleted file mode 100644 index c0f8550d1..000000000 --- a/cmake/Modules/FindLive555.cmake +++ /dev/null @@ -1,75 +0,0 @@ -# Try to find Live555 libraries -# Once done this will define -# Live555_FOUND -# Live555_INCLUDE_DIRS -# Live555_LIBRARIES - -if (NOT Live555_FOUND) - set(_Live555_FOUND ON) - - foreach (library liveMedia BasicUsageEnvironment Groupsock UsageEnvironment) - - string(TOLOWER ${library} lowercase_library) - - find_path(Live555_${library}_INCLUDE_DIR - NAMES - ${library}.hh - ${lowercase_library}.hh - PATHS - ${Live555_ROOT}/${library}/include - ${Live555_ROOT}/live/${library}/include - /usr/include/${library} - /usr/local/include/${library} - /usr/include/${lowercase_library} - /usr/local/include/${lowercase_library} - ) - - if (Live555_${library}_INCLUDE_DIR) - list(APPEND _Live555_INCLUDE_DIRS ${Live555_${library}_INCLUDE_DIR}) - else() - set(_Live555_FOUND OFF) - endif () - - foreach (mode DEBUG RELEASE) - find_library(Live555_${library}_LIBRARY_${mode} - NAMES - ${library} - ${lowercase_library} - PATHS - ${Live555_ROOT}/lib/${mode} - ${Live555_ROOT}/${library} - ) - if (Live555_${library}_LIBRARY_${mode}) - if (${mode} STREQUAL RELEASE) - list(APPEND _Live555_LIBRARIES optimized ${Live555_${library}_LIBRARY_${mode}}) - elseif (${mode} STREQUAL DEBUG) - list(APPEND _Live555_LIBRARIES debug ${Live555_${library}_LIBRARY_${mode}}) - else () - MESSAGE(STATUS no) - list(APPEND _Live555_LIBRARIES ${Live555_${library}_LIBRARY_${mode}}) - endif() - else() - set(_Live555_FOUND OFF) - endif () - endforeach () - - endforeach () - - if (_Live555_FOUND) - set(Live555_INCLUDE_DIRS ${_Live555_INCLUDE_DIRS} CACHE INTERNAL "") - set(Live555_LIBRARIES ${_Live555_LIBRARIES} CACHE INTERNAL "") - set(Live555_FOUND ${_Live555_FOUND} CACHE BOOL "" FORCE) - endif() - - include(FindPackageHandleStandardArgs) - # handle the QUIETLY and REQUIRED arguments and set LOGGING_FOUND to TRUE - # if all listed variables are TRUE - find_package_handle_standard_args(Live555 DEFAULT_MSG Live555_INCLUDE_DIRS Live555_LIBRARIES Live555_FOUND) - - # Tell cmake GUIs to ignore the "local" variables. - mark_as_advanced(Live555_INCLUDE_DIRS Live555_LIBRARIES Live555_FOUND) -endif (NOT Live555_FOUND) - -if (Live555_FOUND) - message(STATUS "Found live555") -endif() diff --git a/cmake/Modules/FindV4L2.cmake b/cmake/Modules/FindV4L2.cmake new file mode 100644 index 000000000..bad2e6793 --- /dev/null +++ b/cmake/Modules/FindV4L2.cmake @@ -0,0 +1,99 @@ +#[=======================================================================[.rst: +FindV4L2 +---------- + +Find V4L2 headers and libv4l2 + + +This module accepts optional COMPONENTS: + + videodev2 (default) + libv4l2 + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines the following :prop_tgt:`IMPORTED` targets:: + +``V4L2::videodev2`` + The Video for Linux Two header file, if found. +``V4L2::libv4l2`` + A thin abstraction layer on top of video4linux2 devices, if found. + +Result Variables +^^^^^^^^^^^^^^^^ + +``V4L2_FOUND`` + System has v4l2 support. If no components are specified only the videodev2.h header has to be found. +``V4L2_INCLUDE_DIRS`` + The v4l2 include directories. +``V4L2_LIBRARIES`` + The libraries needed to have v4l2 support according to the specified components. +#]=======================================================================] + +find_path(V4L2_VIDEODEV2_INCLUDE_DIR + NAMES linux/videodev2.h) +mark_as_advanced(V4L2_VIDEODEV2_INCLUDE_DIR) + +if(EXISTS "${V4L2_VIDEODEV2_INCLUDE_DIR}") + set(V4L2_videodev2_FOUND TRUE) +else() + set(V4L2_videodev2_FOUND FALSE) +endif() + +pkg_check_modules(PC_V4L2_LIBV4L2 QUIET libv4l2) + +find_path(V4L2_LIBV4L2_INCLUDE_DIR + NAMES libv4l2.h + HINTS + ${PC_V4L2_LIBV4L2_INCLUDEDIR} + ${PC_V4L2_LIBV4L2_INCLUDE_DIRS}) +mark_as_advanced(V4L2_LIBV4L2_INCLUDE_DIR) + +find_library(V4L2_LIBV4L2_LIBRARY + NAMES ${PC_V4L2_LIBV4L2_LIBRARIES} + HINTS + ${PC_V4L2_LIBV4L2_LIBDIR} + ${PC_V4L2_LIBV4L2_LIBRARY_DIR}) +mark_as_advanced(V4L2_LIBV4L2_LIBRARY) + +if(EXISTS "${V4L2_LIBV4L2_INCLUDE_DIR}" AND + EXISTS "${V4L2_LIBV4L2_LIBRARY}") + set(V4L2_libv4l2_FOUND TRUE) +else() + set(V4L2_libv4l2_FOUND FALSE) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(V4L2 + REQUIRED_VARS + V4L2_VIDEODEV2_INCLUDE_DIR + HANDLE_COMPONENTS) + +set(V4L2_INCLUDE_DIRS) +set(V4L2_LIBRARIES) + +if(V4L2_videodev2_FOUND) + set(V4L2_VIDEODEV2_INCLUDE_DIRS ${V4L2_VIDEODEV2_INCLUDE_DIR}) + list(APPEND V4L2_INCLUDE2_DIRS + "${V4L2_VIDEODEV2_INCLUDE_DIRS}") + + add_library(V4L2::videodev2 INTERFACE IMPORTED) + set_target_properties(V4L2::videodev2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${V4L2_VIDEODEV2_INCLUDE_DIRS}") +endif() + +if(V4L2_libv4l2_FOUND) + set(V4L2_LIBV4L2_INCLUDE_DIRS ${V4L2_LIBV4L2_INCLUDE_DIR}) + set(V4L2_LIBV4L2_LIBRARIES ${V4L2_LIBV4L2_LIBRARY}) + + list(APPEND V4L2_INCLUDE_DIRS + "${V4L2_LIBV4L2_INCLUDE_DIRS}") + list(APPEND V4L2_LIBRARIES + "${V4L2_LIBV4L2_LIBRARIES}") + + add_library(V4L2::libv4l2 UNKNOWN IMPORTED) + set_target_properties(V4L2::libv4l2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${V4L2_LIBV4L2_INCLUDE_DIRS}" + IMPORTED_LOCATION "${V4L2_LIBV4L2_LIBRARY}") +endif() diff --git a/cmake/compiler/clang/settings.cmake b/cmake/compiler/clang/settings.cmake index 791a58124..d5ba4204a 100644 --- a/cmake/compiler/clang/settings.cmake +++ b/cmake/compiler/clang/settings.cmake @@ -2,7 +2,15 @@ target_compile_options(zm-warning-interface INTERFACE -Wall -Wextra - -Wno-unused-parameter) + -Wimplicit-fallthrough + -Wno-unused-parameter + -Wvla) + +if(ENABLE_WERROR) + target_compile_options(zm-warning-interface + INTERFACE + -Werror) +endif() if(ASAN) target_compile_options(zm-compile-option-interface diff --git a/cmake/compiler/gcc/settings.cmake b/cmake/compiler/gcc/settings.cmake index b037a4ea6..638bc39b4 100644 --- a/cmake/compiler/gcc/settings.cmake +++ b/cmake/compiler/gcc/settings.cmake @@ -1,18 +1,31 @@ target_compile_options(zm-warning-interface INTERFACE -Wall + $<$,5.0>:-Wconditionally-supported> -Wextra + -Wformat-security -Wno-cast-function-type - -Wno-type-limits - -Wno-unused-parameter) + $<$,11>:-Wno-clobbered> + $<$,5.1>:-Wno-missing-field-initializers> + -Wno-unused-parameter + -Woverloaded-virtual + -Wvla) + +if(ENABLE_WERROR) + target_compile_options(zm-warning-interface + INTERFACE + -Werror) +endif() if(ASAN) target_compile_options(zm-compile-option-interface INTERFACE + -D_GLIBCXX_SANITIZE_VECTOR=1 -fno-omit-frame-pointer -fsanitize=address -fsanitize-recover=address - -fsanitize-address-use-after-scope) + -fsanitize-address-use-after-scope + -Wno-stringop-truncation) target_link_options(zm-compile-option-interface INTERFACE 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 574d34440..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 Monitor_Status SET + UPDATE Event_Summaries SET HourEvents = GREATEST(COALESCE(HourEvents,1)-1,0), HourEventDiskSpace=GREATEST(COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 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; + 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 Monitor_Status SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitor_Status.MonitorId=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 Monitor_Status SET + UPDATE Event_Summaries SET DayEvents = GREATEST(COALESCE(DayEvents,1)-1,0), DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 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; + 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 Monitor_Status SET DayEventDiskSpace=GREATEST(COALESCE(DayEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=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 Monitor_Status SET + UPDATE Event_Summaries SET WeekEvents = GREATEST(COALESCE(WeekEvents,1)-1,0), WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 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; + 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 Monitor_Status SET WeekEventDiskSpace=GREATEST(COALESCE(WeekEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=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 Monitor_Status SET + UPDATE Event_Summaries SET MonthEvents = GREATEST(COALESCE(MonthEvents,1)-1,0), MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 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; + 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 Monitor_Status SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Monitor_Status.MonitorId=NEW.MonitorId; + UPDATE Event_Summaries SET MonthEventDiskSpace=GREATEST(COALESCE(MonthEventDiskSpace,0)+diff,0) WHERE Event_Summaries.MonitorId=NEW.MonitorId; END IF; END IF; END; @@ -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 Monitor_Status SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Monitor_Status.MonitorId=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 Monitor_Status + UPDATE Event_Summaries SET ArchivedEvents = GREATEST(COALESCE(ArchivedEvents,0)-1,0), ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 Monitor_Status SET + UPDATE Event_Summaries SET ArchivedEventDiskSpace = GREATEST(COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=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 Monitor_Status + UPDATE Event_Summaries SET TotalEventDiskSpace = GREATEST(COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; @@ -189,13 +190,13 @@ FOR EACH ROW 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 + 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 Monitor_Status.MonitorId=NEW.MonitorId; + TotalEvents = COALESCE(TotalEvents,0)+1; END; // @@ -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 Monitor_Status 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 Monitor_Status.MonitorId=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; ELSE - UPDATE Monitor_Status SET + UPDATE Event_Summaries SET TotalEvents = GREATEST(COALESCE(TotalEvents,1)-1,0), TotalEventDiskSpace=GREATEST(COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0),0) - WHERE Monitor_Status.MonitorId=OLD.MonitorId; + WHERE Event_Summaries.MonitorId=OLD.MonitorId; END IF; END; diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 7a87f2403..5cf6d3573 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -468,6 +468,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), @@ -482,7 +483,7 @@ CREATE TABLE `Monitors` ( `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , `VideoWriter` TINYINT NOT NULL DEFAULT '0', `OutputCodec` int(10) unsigned NOT NULL default 0, - `Encoder` enum('auto','h264','libx264','h264_omx','h264_vaapi','mjpeg','mpeg1','mpeg2'), + `Encoder` varchar(32), `OutputContainer` enum('auto','mp4','mkv'), `EncoderParameters` TEXT, `RecordAudio` TINYINT NOT NULL DEFAULT '0', @@ -496,11 +497,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', @@ -522,6 +524,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', @@ -533,8 +536,10 @@ CREATE TABLE `Monitors` ( `ZoneCount` TINYINT NOT NULL DEFAULT 0, `Refresh` int(10) unsigned default NULL, `Latitude` DECIMAL(10,8), - `Longitude` DECIMAL(10,8), + `Longitude` DECIMAL(11,8), `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, + `RTSPStreamName` varchar(255) NOT NULL default '', + `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal', PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -547,6 +552,12 @@ CREATE TABLE `Monitor_Status` ( `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=@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, @@ -561,6 +572,7 @@ CREATE TABLE `Monitor_Status` ( `ArchivedEventDiskSpace` bigint default NULL, PRIMARY KEY (`MonitorId`) ) ENGINE=@ZM_MYSQL_ENGINE@; + -- -- Table structure for table `States` -- PP - Added IsActive to track custom run states @@ -670,11 +682,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@; @@ -777,7 +791,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 @@ -876,24 +925,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); @@ -1031,11 +1080,11 @@ CREATE TABLE MontageLayouts ( PRIMARY KEY (`Id`) ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"50%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{"float":"left", "width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -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"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); CREATE TABLE Sessions ( id char(32) not null, @@ -1044,6 +1093,26 @@ CREATE TABLE Sessions ( 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.28.99.sql b/db/zm_update-1.28.99.sql index ccbc9c059..4f2054474 100644 --- a/db/zm_update-1.28.99.sql +++ b/db/zm_update-1.28.99.sql @@ -7,318 +7,146 @@ -- Add Controls definition for FI9831W -- Add Controls definition for FI8918W -- -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'ONVIF Camera' as Name, - 'Ffmpeg' as Type, - 'onvif' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 1 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 1 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 1 as CanIris, - 0 as CanAutoIris, - 1 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 255 as MaxIrisRange, - 16 as MinIrisStep, - 16 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 1 as CanWhite, - 0 as CanAutoWhite, - 1 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 6 as MaxWhiteRange, - 1 as MinWhiteStep, - 1 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 1 as HasPresets, - 10 as NumPresets, - 0 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 0 as MaxPanRange, - 0 as MinPanStep, - 0 as MaxPanStep, - 0 as HasPanSpeed, - 0 as MinPanSpeed, - 0 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 0 as MaxTiltRange, - 0 as MinTiltStep, - 0 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'ONVIF Camera' -) LIMIT 1; +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='ONVIF Camera') > 0, +"SELECT 'ONVIF Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon, + MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon, + MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('ONVIF Camera', 'Ffmpeg', 'onvif', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'Foscam 9831W' as Name, - 'Ffmpeg' as Type, - 'FI9831W' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 0 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 0 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 0 as CanIris, - 0 as CanAutoIris, - 0 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 0 as MaxIrisRange, - 0 as MinIrisStep, - 0 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 0 as CanWhite, - 0 as CanAutoWhite, - 0 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 0 as MaxWhiteRange, - 0 as MinWhiteStep, - 0 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 0 as HasPresets, - 16 as NumPresets, - 1 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 360 as MaxPanRange, - 0 as MinPanStep, - 360 as MaxPanStep, - 1 as HasPanSpeed, - 0 as MinPanSpeed, - 4 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 90 as MaxTiltRange, - 0 as MinTiltStep, - 90 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'Foscam 9831W' -) LIMIT 1; + 1, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 1, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 1, /* as CanIris, */ 0, /* as CanAutoIris, */ 1, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 255, /* as MaxIrisRange, */ 16, /* as MinIrisStep, */ 16, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 1, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 1, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 6, /* as MaxWhiteRange, */ 1, /* as MinWhiteStep, */ 1, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 1, /* as HasPresets, */ 10, /* as NumPresets, */ 0, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 0, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 0, /* as MaxPanStep, */ + 0, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 0, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 0, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 0, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" +)); -INSERT INTO Controls -SELECT * FROM (SELECT NULL as Id, - 'Foscam FI8918W' as Name, - 'Ffmpeg' as Type, - 'FI8918W' as Protocol, - 0 as CanWake, - 0 as CanSleep, - 1 as CanReset, - 0 as CanZoom, - 0 as CanAutoZoom, - 0 as CanZoomAbs, - 0 as CanZoomRel, - 0 as CanZoomCon, - 0 as MinZoomRange, - 0 as MaxZoomRange, - 0 as MinZoomStep, - 0 as MaxZoomStep, - 0 as HasZoomSpeed, - 0 as MinZoomSpeed, - 0 as MaxZoomSpeed, - 0 as CanFocus, - 0 as CanAutoFocus, - 0 as CanFocusAbs, - 0 as CanFocusRel, - 0 as CanFocusCon, - 0 as MinFocusRange, - 0 as MaxFocusRange, - 0 as MinFocusStep, - 0 as MaxFocusStep, - 0 as HasFocusSpeed, - 0 as MinFocusSpeed, - 0 as MaxFocusSpeed, - 0 as CanIris, - 0 as CanAutoIris, - 0 as CanIrisAbs, - 0 as CanIrisRel, - 0 as CanIrisCon, - 0 as MinIrisRange, - 0 as MaxIrisRange, - 0 as MinIrisStep, - 0 as MaxIrisStep, - 0 as HasIrisSpeed, - 0 as MinIrisSpeed, - 0 as MaxIrisSpeed, - 0 as CanGain, - 0 as CanAutoGain, - 0 as CanGainAbs, - 0 as CanGainRel, - 0 as CanGainCon, - 0 as MinGainRange, - 0 as MaxGainRange, - 0 as MinGainStep, - 0 as MaxGainStep, - 0 as HasGainSpeed, - 0 as MinGainSpeed, - 0 as MaxGainSpeed, - 0 as CanWhite, - 0 as CanAutoWhite, - 0 as CanWhiteAbs, - 0 as CanWhiteRel, - 0 as CanWhiteCon, - 0 as MinWhiteRange, - 0 as MaxWhiteRange, - 0 as MinWhiteStep, - 0 as MaxWhiteStep, - 0 as HasWhiteSpeed, - 0 as MinWhiteSpeed, - 0 as MaxWhiteSpeed, - 0 as HasPresets, - 8 as NumPresets, - 0 as HasHomePreset, - 1 as CanSetPresets, - 1 as CanMove, - 1 as CanMoveDiag, - 0 as CanMoveMap, - 0 as CanMoveAbs, - 0 as CanMoveRel, - 1 as CanMoveCon, - 1 as CanPan, - 0 as MinPanRange, - 360 as MaxPanRange, - 0 as MinPanStep, - 360 as MaxPanStep, - 1 as HasPanSpeed, - 0 as MinPanSpeed, - 4 as MaxPanSpeed, - 0 as HasTurboPan, - 0 as TurboPanSpeed, - 1 as CanTilt, - 0 as MinTiltRange, - 90 as MaxTiltRange, - 0 as MinTiltStep, - 90 as MaxTiltStep, - 0 as HasTiltSpeed, - 0 as MinTiltSpeed, - 0 as MaxTiltSpeed, - 0 as HasTurboTilt, - 0 as TurboTiltSpeed, - 0 as CanAutoScan, - 0 as NumScanPaths) AS tmp -WHERE NOT EXISTS ( - SELECT Name FROM Controls WHERE name = 'Foscam FI8918W' -) LIMIT 1; +PREPARE stmt FROM @s; +EXECUTE stmt; +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='Foscam 9831W') > 0, +"SELECT 'Foscam 9831W Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon,MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon,MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('Foscam 9831W', 'Ffmpeg', 'FI9831W', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ + 0, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 0, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 0, /* as CanIris, */ 0, /* as CanAutoIris, */ 0, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 0, /* as MaxIrisRange, */ 0, /* as MinIrisStep, */ 0, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 0, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 0, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 0, /* as MaxWhiteRange, */ 0, /* as MinWhiteStep, */ 0, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 0, /* as HasPresets, */ 16, /* as NumPresets, */ 1, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 360, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 360, /* as MaxPanStep, */ + 1, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 4, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 90, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 90, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( (SELECT COUNT(*) FROM Controls WHERE Name='Foscam FI8918W') > 0, +"SELECT 'Foscam 8918W Camera Control already exists in Controls'", +"INSERT INTO Controls ( + Name,Type,Protocol, + CanWake,CanSleep,CanReset, + CanZoom,CanAutoZoom, CanZoomAbs,CanZoomRel,CanZoomCon, + MinZoomRange, MaxZoomRange, MinZoomStep,MaxZoomStep,HasZoomSpeed,MinZoomSpeed,MaxZoomSpeed, + CanFocus,CanAutoFocus,CanFocusAbs,CanFocusRel,CanFocusCon,MinFocusRange,MaxFocusRange,MinFocusStep,MaxFocusStep,HasFocusSpeed,MinFocusSpeed,MaxFocusSpeed, + CanIris,CanAutoIris,CanIrisAbs,CanIrisRel,CanIrisCon,MinIrisRange, MaxIrisRange, MinIrisStep, MaxIrisStep,HasIrisSpeed,MinIrisSpeed,MaxIrisSpeed, + CanGain,CanAutoGain, CanGainAbs,CanGainRel, CanGainCon, + MinGainRange,MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, MaxGainSpeed, + CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, CanWhiteCon, + MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, HasWhiteSpeed,MinWhiteSpeed,MaxWhiteSpeed, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, + CanMove, CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, + CanPan, MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, + HasPanSpeed,MinPanSpeed,MaxPanSpeed,HasTurboPan,TurboPanSpeed,CanTilt, MinTiltRange,MaxTiltRange, MinTiltStep,MaxTiltStep, + HasTiltSpeed,MinTiltSpeed,MaxTiltSpeed, HasTurboTilt,TurboTiltSpeed, + CanAutoScan,NumScanPaths) + VALUES + ('Foscam FI8918W', 'Ffmpeg', 'FI8918W', + 0, /* as CanWake, */ 0, /* as CanSleep, */ 1, /* as CanReset, */ + 0, /* as CanZoom, */ 0, /* as CanAutoZoom, */ 0, /* as CanZoomAbs, */ 0, /* as CanZoomRel, */ 0, /* as CanZoomCon, */ + 0, /* as MinZoomRange, */ 0, /* as MaxZoomRange, */ 0, /* as MinZoomStep, */ 0, /* as MaxZoomStep, */ 0, /* as HasZoomSpeed, */ 0, /* as MinZoomSpeed, */ 0, /* as MaxZoomSpeed, */ + 0, /* as CanFocus, */ 0, /* as CanAutoFocus, */ 0, /* as CanFocusAbs, */ 0, /* as CanFocusRel, */ 0, /* as CanFocusCon, */ + 0, /* as MinFocusRange, */ 0, /* as MaxFocusRange, */ 0, /* as MinFocusStep, */ 0, /* as MaxFocusStep, */ 0, /* as HasFocusSpeed, */ 0, /* as MinFocusSpeed, */ 0, /* as MaxFocusSpeed, */ + 0, /* as CanIris, */ 0, /* as CanAutoIris, */ 0, /* as CanIrisAbs, */ 0, /* as CanIrisRel, */ 0, /* as CanIrisCon, */ + 0, /* as MinIrisRange, */ 0, /* as MaxIrisRange, */ 0, /* as MinIrisStep, */ 0, /* as MaxIrisStep, */ 0, /* as HasIrisSpeed, */ 0, /* as MinIrisSpeed, */ 0, /* as MaxIrisSpeed, */ + 0, /* as CanGain, */ 0, /* as CanAutoGain, */ 0, /* as CanGainAbs, */ 0, /* as CanGainRel, */ 0, /* as CanGainCon, */ + 0, /* as MinGainRange, */ 0, /* as MaxGainRange, */ 0, /* as MinGainStep, */ 0, /* as MaxGainStep, */ 0, /* as HasGainSpeed, */ 0, /* as MinGainSpeed, */ 0, /* as MaxGainSpeed, */ + 0, /* as CanWhite, */ 0, /* as CanAutoWhite, */ 0, /* as CanWhiteAbs, */ 0, /* as CanWhiteRel, */ 0, /* as CanWhiteCon, */ + 0, /* as MinWhiteRange, */ 0, /* as MaxWhiteRange, */ 0, /* as MinWhiteStep, */ 0, /* as MaxWhiteStep, */ 0, /* as HasWhiteSpeed, */ 0, /* as MinWhiteSpeed, */ 0, /* as MaxWhiteSpeed, */ + 0, /* as HasPresets, */ 8, /* as NumPresets, */ 0, /* as HasHomePreset, */ 1, /* as CanSetPresets, */ + 1, /* as CanMove, */ 1, /* as CanMoveDiag, */ 0, /* as CanMoveMap, */ 0, /* as CanMoveAbs, */ 0, /* as CanMoveRel, */ 1, /* as CanMoveCon, */ + 1, /* as CanPan, */ 0, /* as MinPanRange, */ 360, /* as MaxPanRange, */ 0, /* as MinPanStep, */ 360, /* as MaxPanStep, */ + 1, /* as HasPanSpeed, */ 0, /* as MinPanSpeed, */ 4, /* as MaxPanSpeed, */ 0, /* as HasTurboPan, */ 0, /* as TurboPanSpeed, */ + 1, /* as CanTilt, */ 0, /* as MinTiltRange, */ 90, /* as MaxTiltRange, */ 0, /* as MinTiltStep, */ 90, /* as MaxTiltStep, */ + 0, /* as HasTiltSpeed, */ 0, /* as MinTiltSpeed, */ 0, /* as MaxTiltSpeed, */ 0, /* as HasTurboTilt, */ 0, /* as TurboTiltSpeed, */ + 0, /* as CanAutoScan, */ + 0 /* as NumScanPaths */ + )" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; -- -- Hide USE_DEEP_STORAGE from user to prevent accidental event loss -- diff --git a/db/zm_update-1.35.14.sql b/db/zm_update-1.35.14.sql index daa8239ff..f3c8bf779 100644 --- a/db/zm_update-1.35.14.sql +++ b/db/zm_update-1.35.14.sql @@ -28,8 +28,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEvents' ) > 0, -"SELECT 'Column TotalEvents is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEvents`" +"ALTER TABLE `Monitors` DROP `TotalEvents`", +"SELECT 'Column TotalEvents is already removed from Monitors'" )); PREPARE stmt FROM @s; EXECUTE stmt; @@ -50,8 +50,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEventDiskSpace' ) > 0, -"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`" +"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`", +"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'" )); 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.36.6.sql b/db/zm_update-1.36.6.sql new file mode 100644 index 000000000..e75e59610 --- /dev/null +++ b/db/zm_update-1.36.6.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8); diff --git a/db/zm_update-1.37.1.sql b/db/zm_update-1.37.1.sql new file mode 100644 index 000000000..e75e59610 --- /dev/null +++ b/db/zm_update-1.37.1.sql @@ -0,0 +1 @@ +ALTER TABLE `Monitors` MODIFY `Longitude` DECIMAL(11,8); diff --git a/db/zm_update-1.37.2.sql b/db/zm_update-1.37.2.sql new file mode 100644 index 000000000..d06fe204f --- /dev/null +++ b/db/zm_update-1.37.2.sql @@ -0,0 +1,7 @@ +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px","width":"auto"} }' WHERE `Name`='Freeform'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='2 Wide'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"25%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='4 Wide'; +UPDATE MontageLayouts SET `Positions` = '{ "default":{"float":"left", "width":"20%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' WHERE `Name`='5 Wide'; + +UPDATE Monitors set Importance = 'Normal' where Importance IS NULL; +ALTER TABLE `Monitors` MODIFY `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal'; diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt index 588f5d558..792048adf 100644 --- a/dep/CMakeLists.txt +++ b/dep/CMakeLists.txt @@ -1,2 +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/BaseTest.cpp b/dep/jwt-cpp/BaseTest.cpp deleted file mode 100644 index aceeb38d4..000000000 --- a/dep/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/dep/jwt-cpp/CMakeLists.txt b/dep/jwt-cpp/CMakeLists.txt index c2643f583..81ddc84a1 100644 --- a/dep/jwt-cpp/CMakeLists.txt +++ b/dep/jwt-cpp/CMakeLists.txt @@ -3,4 +3,4 @@ add_library(jwt-cpp::jwt-cpp ALIAS jwt-cpp) target_include_directories(jwt-cpp INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/include/jwt-cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/dep/jwt-cpp/ClaimTest.cpp b/dep/jwt-cpp/ClaimTest.cpp deleted file mode 100644 index 749edf2ef..000000000 --- a/dep/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/dep/jwt-cpp/Doxyfile b/dep/jwt-cpp/Doxyfile index e3303d2b9..0e912fd79 100644 --- a/dep/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/dep/jwt-cpp/HelperTest.cpp b/dep/jwt-cpp/HelperTest.cpp deleted file mode 100644 index f998e1289..000000000 --- a/dep/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/dep/jwt-cpp/README.md b/dep/jwt-cpp/README.md index 7636f9f67..5e3903262 100644 --- a/dep/jwt-cpp/README.md +++ b/dep/jwt-cpp/README.md @@ -1,98 +1,208 @@ -# jwt-cpp +# ![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) -A header only library for creating and validating json web tokens in c++. +[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 -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++ +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(int argc, const char** argv) { - std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; - auto decoded = jwt::decode(token); +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.to_json() << std::endl; + 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. -```c++ + +```cpp auto verifier = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{ "secret" }) - .with_issuer("auth0"); + .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++ +Creating a token (and signing) is equally as easy. + +```cpp auto token = jwt::create() - .set_issuer("auth0") - .set_type("JWS") - .set_payload_claim("sample", std::string("test")) - .sign(jwt::algorithm::hs256{"secret"}); + .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 2 hours: +Here is a simple example of creating a token that will expire in one hour: -```c++ +```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"}); +``` - // 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"} +> 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 installed in linker path + +* 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 amd _EVP_sha256 symbols on Mac +### 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 "Windows.h", which is often included in windowsprojects, defines macros for MIN and MAX which screw up std::numeric_limits. + +### 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 +* place `#undef max` and `#undef min` before you include this library diff --git a/dep/jwt-cpp/TestMain.cpp b/dep/jwt-cpp/TestMain.cpp deleted file mode 100644 index b8b7262bc..000000000 --- a/dep/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/dep/jwt-cpp/TokenFormatTest.cpp b/dep/jwt-cpp/TokenFormatTest.cpp deleted file mode 100644 index e670a82c8..000000000 --- a/dep/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/dep/jwt-cpp/TokenTest.cpp b/dep/jwt-cpp/TokenTest.cpp deleted file mode 100644 index 6d2d004c3..000000000 --- a/dep/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/dep/jwt-cpp/include/jwt-cpp/base.h b/dep/jwt-cpp/include/jwt-cpp/base.h index dfca7fc08..c447113c9 100644 --- a/dep/jwt-cpp/include/jwt-cpp/base.h +++ b/dep/jwt-cpp/include/jwt-cpp/base.h @@ -1,39 +1,64 @@ -#pragma once -#include +#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 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 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 = "="; + 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 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 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"; + 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 @@ -44,18 +69,27 @@ namespace jwt { 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) { + 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 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; @@ -65,14 +99,13 @@ namespace jwt { res += alphabet[(triple >> 0 * 6) & 0x3F]; } - if (fast_size == size) - return res; + 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 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; @@ -89,14 +122,14 @@ namespace jwt { res += alphabet[(triple >> 1 * 6) & 0x3F]; res += fill; break; - default: - break; + default: break; } return res; } - static std::string decode(const std::string& base, const std::array& alphabet, const std::string& fill) { + 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; @@ -104,14 +137,12 @@ namespace jwt { 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 (fill_cnt > 2) throw std::runtime_error("Invalid input"); + } else + break; } - if ((size + fill_cnt) % 4 != 0) - throw std::runtime_error("Invalid input"); + if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input"); size_t out_size = size / 4 * 3; std::string res; @@ -119,13 +150,11 @@ namespace jwt { auto get_sextet = [&](size_t offset) { for (size_t i = 0; i < alphabet.size(); i++) { - if (alphabet[i] == base[offset]) - return 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++); @@ -133,36 +162,47 @@ namespace jwt { 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); + 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; + 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; + if (fill_cnt == 0) return res; - uint32_t triple = (get_sextet(fast_size) << 3 * 6) - + (get_sextet(fast_size + 1) << 2 * 6); + 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: + 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/jwt-cpp/jwt_cpp.h b/dep/jwt-cpp/include/jwt-cpp/jwt_cpp.h deleted file mode 100644 index c8c3c8719..000000000 --- a/dep/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/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/dep/jwt-cpp/include/jwt-cpp/picojson.h b/dep/jwt-cpp/include/picojson/picojson.h similarity index 96% rename from dep/jwt-cpp/include/jwt-cpp/picojson.h rename to dep/jwt-cpp/include/picojson/picojson.h index 24a60c5be..76742fe06 100644 --- a/dep/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/jwt-cpp/jwt-cpp.sln b/dep/jwt-cpp/jwt-cpp.sln deleted file mode 100644 index ef5abc2a6..000000000 --- a/dep/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/dep/jwt-cpp/jwt-cpp.vcxproj b/dep/jwt-cpp/jwt-cpp.vcxproj deleted file mode 100644 index 7d791a4d4..000000000 --- a/dep/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/dep/jwt-cpp/jwt-cpp.vcxproj.filters b/dep/jwt-cpp/jwt-cpp.vcxproj.filters deleted file mode 100644 index 6419b2d22..000000000 --- a/dep/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/dep/jwt-cpp/vcpkg/CONTROL b/dep/jwt-cpp/vcpkg/CONTROL deleted file mode 100644 index d29834fc6..000000000 --- a/dep/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/dep/jwt-cpp/vcpkg/fix-picojson.patch b/dep/jwt-cpp/vcpkg/fix-picojson.patch deleted file mode 100644 index 44c04fe58..000000000 --- a/dep/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/dep/jwt-cpp/vcpkg/fix-warning.patch b/dep/jwt-cpp/vcpkg/fix-warning.patch deleted file mode 100644 index d013a7782..000000000 --- a/dep/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/dep/jwt-cpp/vcpkg/portfile.cmake b/dep/jwt-cpp/vcpkg/portfile.cmake deleted file mode 100644 index 1e10e3c21..000000000 --- a/dep/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/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 28135f583..50d25a0c4 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 - ,libavdevice-dev ,libavcodec-dev ,libavformat-dev ,libavutil-dev @@ -14,7 +13,6 @@ Build-Depends: debhelper, sphinx-doc, dh-linktree, dh-apache2 ,ffmpeg ,net-tools ,libbz2-dev - ,libgcrypt20-dev ,libcurl4-gnutls-dev ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat diff --git a/distros/beowulf/copyright b/distros/beowulf/copyright index a932efa2f..f9a69959e 100644 --- a/distros/beowulf/copyright +++ b/distros/beowulf/copyright @@ -41,7 +41,6 @@ Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/debian/control b/distros/debian/control index 6bb59f206..221a1dda5 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -9,7 +9,6 @@ Build-Depends: debhelper (>= 9), cmake , libjpeg8-dev | libjpeg-dev , libpcre3-dev , libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev - , libavdevice-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , ffmpeg | libav-tools @@ -17,7 +16,7 @@ Build-Depends: debhelper (>= 9), cmake , libnetpbm10-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev - , libgcrypt11-dev, libpolkit-gobject-1-dev + , libpolkit-gobject-1-dev , libphp-serialization-perl , libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl , libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl @@ -47,7 +46,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libnumber-bytes-human-perl ,libfile-slurp-perl , libpcre3 - , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 + , ffmpeg | libav-tools , rsyslog | system-log-daemon , netpbm , zip diff --git a/distros/opensuse/redalert.wav b/distros/opensuse/redalert.wav index 7469d4f3b..56a0ee9e4 120000 --- a/distros/opensuse/redalert.wav +++ b/distros/opensuse/redalert.wav @@ -1 +1 @@ -../redhat/misc/redalert.wav \ No newline at end of file +../redhat/common/redalert.wav \ No newline at end of file diff --git a/distros/opensuse/zoneminder.cmake.OS13.spec b/distros/opensuse/zoneminder.cmake.OS13.spec index e1ed14325..416f59662 100644 --- a/distros/opensuse/zoneminder.cmake.OS13.spec +++ b/distros/opensuse/zoneminder.cmake.OS13.spec @@ -27,7 +27,7 @@ Source: ZoneMinder-%{version}.tar.gz BuildRequires: cmake polkit-devel BuildRequires: perl-DBI perl-DBD-mysql perl-Date-Manip perl-Sys-Mmap -BuildRequires: libjpeg62 libjpeg62-devel libmysqld-devel libSDL-devel libgcrypt-devel libgnutls-devel +BuildRequires: libjpeg62 libjpeg62-devel libmysqld-devel libSDL-devel libgnutls-devel BuildRequires: libffmpeg-devel x264 BuildRequires: pcre-devel w32codec-all diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 86b72b74f..649c27dbf 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -8,27 +8,35 @@ # 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 # This will tell zoneminder's cmake process we are building against a known distro %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} -# Fedora needs apcu backwards compatibility module -%if 0%{?fedora} -%global with_apcu_bc 1 -%endif - # Newer php's keep json functions in a subpackage %if 0%{?fedora} || 0%{?rhel} >= 8 %global with_php_json 1 %endif +# el7 uses cmake3 package and macros +%if 0%{?rhel} == 7 +%global cmake %{cmake3} +%global cmake_build %{cmake3_build} +%global cmake_install %{cmake3_install} +%global cmake_pkg_name cmake3 +%else +%global cmake_pkg_name cmake +%endif + # The default for everything but el7 these days %global _hardened_build 1 Name: zoneminder -Version: 1.35.18 +Version: 1.37.1 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons @@ -39,19 +47,21 @@ 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/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 BuildRequires: mariadb-devel BuildRequires: perl-podlators BuildRequires: polkit-devel -BuildRequires: cmake3 +BuildRequires: %{cmake_pkg_name} BuildRequires: gnutls-devel BuildRequires: bzip2-devel BuildRequires: pcre-devel @@ -112,8 +122,8 @@ Requires: php-mysqli Requires: php-common Requires: php-gd %{?with_php_json:Requires: php-json} -Requires: php-pecl-apcu -%{?with_apcu_bc:Requires: php-pecl-apcu-bc} +%{?fedora:Requires: php-pecl-memcached} +%{?rhel:Requires: php-pecl-apcu} Requires: cambozola Requires: net-tools Requires: psmisc @@ -199,6 +209,10 @@ 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 @@ -209,16 +223,16 @@ mv -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Beh # See https://fedoraproject.org/wiki/LTOByDefault %define _lto_cflags %{nil} -%cmake3 \ +%cmake \ -DZM_WEB_USER="%{zmuid_final}" \ -DZM_WEB_GROUP="%{zmgid_final}" \ -DZM_TARGET_DISTRO="%{zmtargetdistro}" \ . -%cmake3_build +%cmake_build %install -%cmake3_install +%cmake_install desktop-file-install \ --dir %{buildroot}%{_datadir}/applications \ @@ -362,6 +376,7 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %{_bindir}/zmonvif-trigger.pl %{_bindir}/zmstats.pl %{_bindir}/zmrecover.pl +%{_bindir}/zm_rtsp_server %{perl_vendorlib}/ZoneMinder* %{perl_vendorlib}/ONVIF* @@ -417,6 +432,116 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder %changelog +* Mon Jul 05 2021 Andrew Bauer - 1.37.1-1 +- 1.37.x development build + +* Tue Jun 22 2021 Andrew Bauer - 1.36.5-1 +- 1.36.5 release + +* Fri Jun 18 2021 Andrew Bauer - 1.36.4-2 +- apcu-bc deprecated on fedora, use memcached instead +- only refer to cmake3 when building on el7 + +* Tue Jun 08 2021 Andrew Bauer - 1.36.4-1 +- 1.36.4 release + +* Sun May 30 2021 Andrew Bauer - 1.36.3-1 +- 1.36.3 release + +* Fri May 28 2021 Andrew Bauer - 1.36.2-1 +- 1.36.2 release + +* Fri May 21 2021 Andrew Bauer - 1.36.1-1 +- 1.36.1 release +- add rtspserver submodule + +* Wed Apr 21 2021 Andrew Bauer - 1.34.26-1 +- 1.34.26 Release + +* Sun Apr 18 2021 Andrew Bauer - 1.34.24-1 +- 1.34.24 Release + +* Thu Feb 04 2021 RPM Fusion Release Engineering - 1.34.23-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Sun Jan 31 2021 Andrew Bauer - 1.34.23-1 +- 1.34.23 Release +- create ssl cert in all cases, not just nginx + +* Fri Jan 1 2021 Leigh Scott - 1.34.22-3 +- Rebuilt for new ffmpeg snapshot + +* Fri Nov 27 2020 Sérgio Basto - 1.34.22-2 +- Mass rebuild for x264-0.161 + +* Mon Sep 28 2020 Andrew Bauer - 1.34.22-1 +- 1.34.22 Release + +* Mon Sep 28 2020 Andrew Bauer - 1.34.21-1 +- 1.34.21 Release + +* Sun Aug 23 2020 Andrew Bauer - 1.34.20-1 +- 1.34.20 Release +- Buildrequire epel-rpm-macros on rhel +- Update license references for js libraries + +* Wed Aug 19 2020 RPM Fusion Release Engineering - 1.34.18-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Thu Aug 06 2020 Andrew Bauer - 1.34.18-1 +- 1.34.18 Release + +* Thu Aug 06 2020 Andrew Bauer - 1.34.17-2 +- Disable LTO due to top level asm + +* Wed Aug 05 2020 Andrew Bauer - 1.34.17-1 +- 1.34.17 Release + +* Tue Aug 04 2020 Andrew Bauer - 1.34.16-4 +- Use new cmake build macros for f33 compat + +* Tue Jul 07 2020 Sérgio Basto - 1.34.16-3 +- Mass rebuild for x264 + +* Fri Jul 03 2020 Leigh Scott - 1.34.16-2 +- Perl 5.32 rebuild + +* Fri Jun 05 2020 Andrew Bauer - 1.34.16-1 +- 1.34.16 Release + +* Fri May 15 2020 Andrew Bauer - 1.34.14-1 +- 1.34.14 Release + +* Thu May 14 2020 Andrew Bauer - 1.34.13-1 +- 1.34.13 Release + +* Sun May 10 2020 Andrew Bauer - 1.34.12-1 +- 1.34.12 Release + +* Sat May 2 2020 Andrew Bauer - 1.34.11-1 +- 1.34.11 Release + +* Sun Apr 26 2020 Andrew Bauer - 1.34.10-1 +- 1.34.10 Release + +* Mon Apr 6 2020 Andrew Bauer - 1.34.9-1 +- 1.34.9 Release + +* Sat Mar 28 2020 Andrew Bauer - 1.34.7-1 +- 1.34.7 Release + +* Mon Mar 23 2020 Andrew Bauer - 1.34.6-1 +- 1.34.6 Release + +* Sat Feb 29 2020 Andrew Bauer - 1.34.5-1 +- 1.34.5 Release + +* Sun Feb 23 2020 Andrew Bauer - 1.34.3-1 +- 1.34.3 Release + +* Sat Feb 22 2020 RPM Fusion Release Engineering - 1.34.2-2 +- Rebuild for ffmpeg-4.3 git + * 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 d4be60413..1345c39ee 100644 --- a/distros/ubuntu1504_cmake_split_packages/control +++ b/distros/ubuntu1504_cmake_split_packages/control @@ -8,14 +8,14 @@ Build-Depends: debhelper (>= 9), po-debconf (>= 1.0), autoconf, automake, libtoo , libdate-manip-perl, libwww-perl , libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev , libpcre3-dev -, libavcodec-ffmpeg-dev, libavformat-ffmpeg-dev, libswscale-ffmpeg-dev, libavutil-ffmpeg-dev, libavdevice-ffmpeg-dev +, libavcodec-ffmpeg-dev, libavformat-ffmpeg-dev, libswscale-ffmpeg-dev, libavutil-ffmpeg-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , libsys-mmap-perl , libdevice-serialport-perl, libarchive-zip-perl, libmime-lite-perl , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev -, libgcrypt11-dev | libgcrypt20-dev, libpolkit-gobject-1-dev +, libpolkit-gobject-1-dev , libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl Standards-Version: 3.9.6 Homepage: http://www.zoneminder.com/ diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index 4c7164c48..218ab3c17 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 -CXXFLAGS = -DHAVE_LIBCRYPTO ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) DEBOPT = --enable-debug @@ -72,7 +71,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/NEWS b/distros/ubuntu1604/NEWS deleted file mode 100644 index 6200726cf..000000000 --- a/distros/ubuntu1604/NEWS +++ /dev/null @@ -1,10 +0,0 @@ -zoneminder (1.28.1-1) unstable; urgency=low - - This version is no longer automatically initialize or upgrade database. - See README.Debian for details. - - Changed installation paths (please correct your web server configuration): - /usr/share/zoneminder --> /usr/share/zoneminder/www - /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:12:17 +1100 diff --git a/distros/ubuntu1604/README.Debian b/distros/ubuntu1604/README.Debian deleted file mode 100644 index 4fe3464d2..000000000 --- a/distros/ubuntu1604/README.Debian +++ /dev/null @@ -1,130 +0,0 @@ -Zoneminder for Debian ---------------------- - -Initializing database ---------------------- - - pv /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf -OR - cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf - - echo 'grant lock tables,alter,create,index,select,insert,update,delete on zm.* to 'zmuser'@localhost identified by "zmpass";'\ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -Hint: generate secure password with `pwgen` and update "/etc/zm/zm.conf" -accordingly. - -The following command can help to ensure that zoneminder can read its -configuration file: - - chgrp -c www-data /etc/zm/zm.conf - - -Upgrading database ------------------- - -The database is updated automatically on installation. You should not need to take this step. - -Assuming that database is on "localhost" then the following command can be -used to upgrade "zm" database: - - zmupdate.pl - -Additional permissions may be required to perform upgrade: - - echo 'grant lock tables, create, alter on zm.* to 'zmuser'@localhost identified by "zmpass";'\ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -The following command prints the current version of zoneminder database: - - echo 'select Value from Config where Name = "ZM_DYN_CURR_VERSION";' \ - | sudo mysql --defaults-file=/etc/mysql/debian.cnf --skip-column-names zm - - -Enabling service ----------------- - -By default Zoneminder service is not automatically started and needs to be -manually enabled once database is configured: - - sudo systemctl enable zoneminder.service - - -Web server set-up ------------------ - -There are few manual steps to get the web interface working: - -## Apache2 - -Apache can be configured as folder "/zm" using sample .conf: - - sudo a2enconf zoneminder - -Alternatively Apache web site configuration template can be used to setup -zoneminder as "http://zoneminder": - - sudo cp -v /usr/share/doc/zoneminder/examples/apache.conf /etc/apache2/sites-available/ - sudo a2ensite zoneminder.conf - -Common configuration steps for Apache2: - - sudo a2enmod cgi - sudo service apache2 reload - - -## nginx / fcgiwrap - -Nginx needs "php-fpm" package to support PHP and "fcgiwrap" package -for binary "cgi-bin" applications: - - sudo apt-get install php-fpm fcgiwrap - -To enable a URL alias that makes Zoneminder available from - - http://yourserver/zm - -the following line is to be added to "server" section of a web site -configuration: - - include /usr/share/doc/zoneminder/examples/nginx.conf; - -For "default" web site it would be sufficient to include the above -statement to the file - - /etc/nginx/sites-enabled/default - -To avoid problems with feeds from multiple cameras "fcgiwrap" should be -configured to start at least as many processes as there are cameras. -It can be done by adjusting DAEMON_OPTS in "/etc/default/fcgiwrap". -Systemd users may be affected by the following bug: - - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=792705 - - -## Note: - -When Zoneminder web site is running it may be necessary to set -Options/Paths/PATH_ZMS to "/zm/cgi-bin/nph-zms" or according to chosen web -site configuration. - - -Changing the location for images and events -------------------------------------------- - -ZoneMinder is now able to be configured to use an alternative location for storing -events and images at compile time. This package makes use of that, so symlinks in -/usr/share/zoneminder/www are no longer necessary. - -Access to /dev/video* ---------------------- - -For cameras which require access to /dev/video*, zoneminder may need the -www-data user added to the video group in order to see those cameras: - - adduser www-data video - -Note that all web applications running on the zoneminder server will then have -access to all video devices on the system. - - -- Vagrant Cascadian Sun, 27 Mar 2011 13:06:56 -0700 diff --git a/distros/ubuntu1604/TODO.Debian b/distros/ubuntu1604/TODO.Debian deleted file mode 100644 index 9dc59613b..000000000 --- a/distros/ubuntu1604/TODO.Debian +++ /dev/null @@ -1,12 +0,0 @@ - -## Separate substantial /usr/share into its own arch-all package. - -## Decide how to handle database updates. - - * Consider possibility that database may be on another machine (#469239). - * Consider dbconfig-common? Probably not (what if database is not on localhost?). - -### Run `zmupdate.pl` from service control scripts (init.d, service) on start? - - Automatic upgrade will break "one DB, many zoneminders" setup (unimportant?). - diff --git a/distros/ubuntu1604/changelog b/distros/ubuntu1604/changelog deleted file mode 100644 index 0fc2fda2e..000000000 --- a/distros/ubuntu1604/changelog +++ /dev/null @@ -1,3 +0,0 @@ -zoneminder (1.35.6~20200825.27-xenial) xenial; urgency=low - * - -- Isaac Connor Tue, 25 Aug 2020 09:28:18 -0400 diff --git a/distros/ubuntu1604/clean b/distros/ubuntu1604/clean deleted file mode 100644 index 941ef2a3a..000000000 --- a/distros/ubuntu1604/clean +++ /dev/null @@ -1,3 +0,0 @@ -.gitattributes -web/api/.gitattributes -web/api/.gitignore diff --git a/distros/ubuntu1604/compat b/distros/ubuntu1604/compat deleted file mode 100644 index ec635144f..000000000 --- a/distros/ubuntu1604/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/ubuntu1604/conf/apache2/zoneminder.conf deleted file mode 100644 index e3164d36c..000000000 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ /dev/null @@ -1,57 +0,0 @@ -# Remember to enable cgi mod (i.e. "a2enmod cgi"). -ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" - - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - AllowOverride All - Require all granted - - - -# Order matters. This alias must come first. -Alias /zm/cache /var/cache/zoneminder/cache - - Options -Indexes +FollowSymLinks - AllowOverride None - - # Apache 2.4 - Require all granted - - - # Apache 2.2 - Order deny,allow - Allow from all - - - -Alias /zm /usr/share/zoneminder/www - - Options -Indexes +FollowSymLinks - - DirectoryIndex index.php - - - -# For better visibility, the following directives have been migrated from the -# default .htaccess files included with the CakePHP project. -# Parameters not set here are inherited from the parent directive above. - - RewriteEngine on - RewriteRule ^$ app/webroot/ [L] - RewriteRule (.*) app/webroot/$1 [L] - RewriteBase /zm/api - - - - RewriteEngine on - RewriteRule ^$ webroot/ [L] - RewriteRule (.*) webroot/$1 [L] - RewriteBase /zm/api - - - - RewriteEngine On - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - RewriteBase /zm/api - diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control deleted file mode 100644 index d43b50bd0..000000000 --- a/distros/ubuntu1604/control +++ /dev/null @@ -1,167 +0,0 @@ -Source: zoneminder -Section: net -Priority: optional -Maintainer: Isaac Connor -Uploaders: Isaac Connor -Build-Depends: debhelper (>= 9), dh-systemd, python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2 - ,cmake - ,libavdevice-dev (>= 6:10~) - ,libavcodec-dev (>= 6:10~) - ,libavformat-dev (>= 6:10~) - ,libavutil-dev (>= 6:10~) - ,libswresample-dev | libavresample-dev - ,libswscale-dev (>= 6:10~) - ,ffmpeg | libav-tools - ,net-tools - ,libbz2-dev - ,libgcrypt-dev | libgcrypt11-dev - ,libcurl4-gnutls-dev - ,libgnutls-openssl-dev - ,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev - ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat - ,libpcre3-dev - ,libpolkit-gobject-1-dev - ,libv4l-dev (>= 0.8.3) [!hurd-any] - ,libvlc-dev - ,libdate-manip-perl - ,libdbd-mysql-perl - ,libphp-serialization-perl - ,libsys-mmap-perl [!hurd-any] - ,libwww-perl - ,libdata-uuid-perl - ,libssl-dev - ,libcrypt-eksblowfish-perl - ,libdata-entropy-perl - ,libvncserver-dev -# Unbundled (dh_linktree): - ,libjs-jquery -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 - -Package: zoneminder -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} - ,javascript-common - ,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 - ,libswresample2|libswresample3|libswresample24|libswresample-ffmpeg1 - ,ffmpeg | libav-tools - ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl - ,libdbd-mysql-perl - ,libphp-serialization-perl - ,libmodule-load-conditional-perl - ,libnet-sftp-foreign-perl - ,libarchive-zip-perl - ,libdbd-mysql-perl - ,libdevice-serialport-perl - ,libimage-info-perl - ,libjson-maybexs-perl - ,libsys-mmap-perl [!hurd-any] - ,liburi-encode-perl - ,libwww-perl, liburi-perl - ,libdata-dump-perl - ,libdatetime-perl - ,libclass-std-fast-perl - ,libsoap-wsdl-perl - ,libio-socket-multicast-perl - ,libdigest-sha-perl - ,libsys-cpu-perl, libsys-meminfo-perl - ,libdata-uuid-perl - ,libnumber-bytes-human-perl - ,libfile-slurp-perl - ,mysql-client | mariadb-client | virtual-mysql-client - ,perl-modules - ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc, php-json | php5-json - ,policykit-1 - ,rsyslog | system-log-daemon - ,zip - ,libpcre3 - ,libssl | libssl1.0.0 | libssl1.1 - ,libcrypt-eksblowfish-perl - ,libdata-entropy-perl - ,libvncclient1|libvncclient0 -Recommends: ${misc:Recommends} - ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm - ,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 - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - -#Package: libzoneminder-perl -#Section: perl -#Architecture: all -#Multi-Arch: foreign -#Depends: ${misc:Depends}, ${perl:Depends} -# ,libarchive-zip-perl -# ,libdbd-mysql-perl -# ,libdevice-serialport-perl -# ,libimage-info-perl -# ,libjson-maybexs-perl -# ,libsys-mmap-perl [!hurd-any] -# ,liburi-encode-perl -# ,libwww-perl -#Description: ZoneMinder Perl libraries -# ZoneMinder is intended for use in single or multi-camera video security -# applications, including commercial or home CCTV, theft prevention and child -# or family member or home monitoring and other care scenarios. It -# supports capture, analysis, recording, and monitoring of video data coming -# from one or more video or network cameras attached to a Linux system. -# ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom -# cameras using a variety of protocols. It is suitable for use as a home -# video security system and for commercial or professional video security -# and surveillance. It can also be integrated into a home automation system -# via X.10 or other protocols. -# . -# This package provides ZoneMinder Perl libraries; it can be used to -# write custom interfaces as well. - -Package: zoneminder-doc -Section: doc -Architecture: all -Multi-Arch: foreign -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 - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - . - This package provides ZoneMinder documentation in HTML format. - -Package: zoneminder-dbg -Section: debug -Priority: extra -Architecture: any -Depends: zoneminder (= ${binary:Version}), ${misc:Depends} -Description: Zoneminder -- debugging symbols - ZoneMinder is intended for use in single or multi-camera video security - applications, including commercial or home CCTV, theft prevention and child - or family member or home monitoring and other care scenarios. It - supports capture, analysis, recording, and monitoring of video data coming - from one or more video or network cameras attached to a Linux system. - ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom - cameras using a variety of protocols. It is suitable for use as a home - video security system and for commercial or professional video security - and surveillance. It can also be integrated into a home automation system - via X.10 or other protocols. - . - This package provides debugging symbols diff --git a/distros/ubuntu1604/copyright b/distros/ubuntu1604/copyright deleted file mode 100644 index a932efa2f..000000000 --- a/distros/ubuntu1604/copyright +++ /dev/null @@ -1,168 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: ZoneMinder -Upstream-Contact: Philip Coombes -Source: https://github.com/ZoneMinder/ZoneMinder -Comment: - This package was originally debianized by matrix - on Mon, 7 Mar 2005 02:07:57 -0500. - It was re-done for submission to the Debian project by Peter Howard - on Fri, 8 Dec 2006 10:19:43 +1100 -Files-Excluded: - web/skins/*/js/jquery-* - -Files: * -Copyright: 2001-2014 Philip Coombes - 2008 Brian Rudy - 2014 Vincent Giovannone - 2013 Tim Craig - 2003-2008 Corey DeLasaux - 2001-2010 Chris Kistner -License: GPL-2+ - -Files: distros/* -Copyright: 2001-2008 Philip Coombes - 2014 Isaac Connor - 2005 Serg Oskin -License: GPL-2+ - -Files: web/skins/*/js/jquery-* -Copyright: 2010 John Resig - 2010 The Dojo Foundation -License: GPL-2 or Expat -Comment: - Dual licensed under the MIT or GPL Version 2 licenses. - http://jquery.org/license - . - Includes Sizzle.js http://sizzlejs.com/ - Released under the MIT, BSD, and GPL Licenses. - -Files: web/api/* -Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) -License: Expat - -Files: - cmake/Modules/CheckPrototypeDefinition*.cmake - cmake/Modules/FindGLIB2.cmake - cmake/Modules/FindPolkit.cmake - cmake/Modules/GNUInstallDirs.cmake -Copyright: - 2005-2011 Kitware, Inc. - 2010-2011 Andreas Schneider - 2009 Dario Freddi - 2008 Laurent Montel, - 2011 Nikita Krupen'ko -License: BSD-3-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - . - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - . - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - . - * The names of Kitware, Inc., the Insight Consortium, or the names of - any consortium members, or of any contributors, may not be used to - endorse or promote products derived from this software without - specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Files: cmake/Modules/FindPerlModules.cmake -Copyright: 2012 Iowa State University -License: Boost-1.0 - 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. - -Files: debian/* -Copyright: 2015 Dmitry Smirnov - 2007-2014 Peter Howard - 2010-2012 Vagrant Cascadian - 2001-2008 Philip Coombes -License: GPL-2+ - -License: Expat - 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. - -License: GPL-2+ - This package 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 package 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 package; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - . - The complete text of the GNU General Public License version 2 - can be found in "/usr/share/common-licenses/GPL-2". - -License: GPL-2 - This package 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; version 2 of the License. - . - This package 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 package; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - . - The complete text of the GNU General Public License version 2 - can be found in "/usr/share/common-licenses/GPL-2". diff --git a/distros/ubuntu1604/examples/nginx.conf b/distros/ubuntu1604/examples/nginx.conf deleted file mode 100644 index 5636ca3e1..000000000 --- a/distros/ubuntu1604/examples/nginx.conf +++ /dev/null @@ -1,32 +0,0 @@ -location /zm/cgi-bin { - gzip off; - alias /usr/lib/zoneminder/cgi-bin; - - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_pass unix:/var/run/fcgiwrap.socket; -} - -location /zm { -# if ($scheme ~ ^http:){ -# rewrite ^(.*)$ https://$host$1 permanent; -# } - - gzip off; - alias /usr/share/zoneminder/www; - index index.php; - - location ~ \.php$ { - if (!-f $request_filename) { return 404; } - expires epoch; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_index index.php; - fastcgi_pass unix:/var/run/php5-fpm.sock; - } - - location ~ \.(jpg|jpeg|gif|png|ico)$ { - access_log off; - expires 33d; - } -} diff --git a/distros/ubuntu1604/gbp.conf b/distros/ubuntu1604/gbp.conf deleted file mode 100644 index 4608913d9..000000000 --- a/distros/ubuntu1604/gbp.conf +++ /dev/null @@ -1,7 +0,0 @@ - -[dch] -id-length = 0 - -[import-orig] -pristine-tar = False -merge = False diff --git a/distros/ubuntu1604/libzoneminder-perl.install b/distros/ubuntu1604/libzoneminder-perl.install deleted file mode 100644 index 67191d9cf..000000000 --- a/distros/ubuntu1604/libzoneminder-perl.install +++ /dev/null @@ -1,2 +0,0 @@ -usr/share/man/man3 -usr/share/perl5 diff --git a/distros/ubuntu1604/patches/series b/distros/ubuntu1604/patches/series deleted file mode 100644 index e69de29bb..000000000 diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules deleted file mode 100755 index 9a16b1f8f..000000000 --- a/distros/ubuntu1604/rules +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/make -f -# -*- makefile -*- - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_LDFLAGS_MAINT_APPEND += -Wl,--as-needed - -ifeq ($(DEB_BUILD_ARCH_OS),hurd) -ARGS:= -DZM_NO_MMAP=ON -endif - -%: - dh $@ --parallel --buildsystem=cmake --builddirectory=dbuild \ - --with systemd,sphinxdoc,apache2,linktree - -override_dh_auto_configure: - dh_auto_configure -- $(ARGS) \ - -DCMAKE_VERBOSE_MAKEFILE=ON \ - -DCMAKE_BUILD_TYPE=Release \ - -DZM_CONFIG_DIR="/etc/zm" \ - -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ - -DZM_RUNDIR="/run/zm" \ - -DZM_SOCKDIR="/run/zm" \ - -DZM_TMPDIR="/tmp/zm" \ - -DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \ - -DZM_CACHEDIR="/var/cache/zoneminder/cache" \ - -DZM_DIR_EVENTS="/var/cache/zoneminder/events" \ - -DZM_PATH_SHUTDOWN="/sbin/shutdown" \ - -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" - -override_dh_clean: - dh_clean $(MANPAGES1) - $(RM) -r docs/_build - -build-indep: - #$(MAKE) -C docs text - $(MAKE) -C docs html - -MANPAGES1 = dbuild/scripts/zmupdate.pl.1 -$(MANPAGES1): - # generate man page(s): - pod2man -s1 --stderr --utf8 $(patsubst %.1, %, $@) $@ - -## reproducible build: -LAST_CHANGE=$(shell dpkg-parsechangelog -S Date) -BUILD_DATE=$(shell LC_ALL=C date -u "+%B %d, %Y" -d "$(LAST_CHANGE)") -override_dh_installman: $(MANPAGES1) - $(MAKE) -C docs man SPHINXOPTS="-D today=\"$(BUILD_DATE)\"" - dh_installman --language=C $(MANPAGES1) - -override_dh_auto_install: - dh_auto_install --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 - # remove empty directories: - find $(CURDIR)/debian/tmp/usr -type d -empty -delete -printf 'removed %p\n' - # remove extra-license-file: - $(RM) -v $(CURDIR)/debian/tmp/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt - -override_dh_fixperms: - dh_fixperms - # - # As requested by the Debian Webapps Policy Manual §3.2.1 - 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_systemd_enable: - dh_systemd_enable --no-enable - -override_dh_apache2: - dh_apache2 --noenable - -override_dh_strip: - [ -d "$(CURDIR)/debian/zoneminder-dbg" ] \ - && 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/ubuntu1604/source/format b/distros/ubuntu1604/source/format deleted file mode 100644 index 163aaf8d8..000000000 --- a/distros/ubuntu1604/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/distros/ubuntu1604/source/lintian-overrides b/distros/ubuntu1604/source/lintian-overrides deleted file mode 100644 index f905a5a2f..000000000 --- a/distros/ubuntu1604/source/lintian-overrides +++ /dev/null @@ -1,5 +0,0 @@ -## We're using "libjs-jquery" instead. -source-is-missing web/skins/*/js/jquery-3.5.1.min.js - -## Acknowledged, will repack eventually. -source-contains-prebuilt-javascript-object web/skins/*/js/jquery-3.5.1.min.js diff --git a/distros/ubuntu1604/zoneminder-doc.doc-base b/distros/ubuntu1604/zoneminder-doc.doc-base deleted file mode 100644 index c43dc4336..000000000 --- a/distros/ubuntu1604/zoneminder-doc.doc-base +++ /dev/null @@ -1,8 +0,0 @@ -Document: zoneminder-doc -Title: Zoneminder documentation -Abstract: This document describes how to use Zoneminder. -Section: System/Administration - -Format: HTML -Index: /usr/share/doc/zoneminder-doc/html/index.html -Files: /usr/share/doc/zoneminder-doc/html/* diff --git a/distros/ubuntu1604/zoneminder-doc.install b/distros/ubuntu1604/zoneminder-doc.install deleted file mode 100644 index c19bc6f3a..000000000 --- a/distros/ubuntu1604/zoneminder-doc.install +++ /dev/null @@ -1 +0,0 @@ -docs/_build/html usr/share/doc/zoneminder-doc/ diff --git a/distros/ubuntu1604/zoneminder-doc.links b/distros/ubuntu1604/zoneminder-doc.links deleted file mode 100644 index cc09f6462..000000000 --- a/distros/ubuntu1604/zoneminder-doc.links +++ /dev/null @@ -1,2 +0,0 @@ -## Convenience symlink: -/usr/share/doc/zoneminder-doc/html /usr/share/doc/zoneminder/html diff --git a/distros/ubuntu1604/zoneminder.apache2 b/distros/ubuntu1604/zoneminder.apache2 deleted file mode 100644 index 466144fa7..000000000 --- a/distros/ubuntu1604/zoneminder.apache2 +++ /dev/null @@ -1 +0,0 @@ -conf debian/conf/apache2/zoneminder.conf nginx diff --git a/distros/ubuntu1604/zoneminder.bug-presubj b/distros/ubuntu1604/zoneminder.bug-presubj deleted file mode 100644 index 990fc1d94..000000000 --- a/distros/ubuntu1604/zoneminder.bug-presubj +++ /dev/null @@ -1,5 +0,0 @@ -Unless bug is specific to Debian please consider reporting it directly to -upstream developer(s): - - https://github.com/ZoneMinder/ZoneMinder/issues - diff --git a/distros/ubuntu1604/zoneminder.dirs b/distros/ubuntu1604/zoneminder.dirs deleted file mode 100644 index 3c7237bf3..000000000 --- a/distros/ubuntu1604/zoneminder.dirs +++ /dev/null @@ -1,10 +0,0 @@ -var/log/zm -var/lib/zm -var/cache/zoneminder/events -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.docs b/distros/ubuntu1604/zoneminder.docs deleted file mode 100644 index b43bf86b5..000000000 --- a/distros/ubuntu1604/zoneminder.docs +++ /dev/null @@ -1 +0,0 @@ -README.md diff --git a/distros/ubuntu1604/zoneminder.examples b/distros/ubuntu1604/zoneminder.examples deleted file mode 100644 index 3b8befe7b..000000000 --- a/distros/ubuntu1604/zoneminder.examples +++ /dev/null @@ -1,2 +0,0 @@ -debian/examples/* -dbuild/misc/apache.conf diff --git a/distros/ubuntu1604/zoneminder.install b/distros/ubuntu1604/zoneminder.install deleted file mode 100644 index 17364c744..000000000 --- a/distros/ubuntu1604/zoneminder.install +++ /dev/null @@ -1,12 +0,0 @@ -etc/zm/zm.conf -etc/zm/conf.d/* -usr/bin -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 -usr/share/perl5 diff --git a/distros/ubuntu1604/zoneminder.links b/distros/ubuntu1604/zoneminder.links deleted file mode 100644 index b7258c3c4..000000000 --- a/distros/ubuntu1604/zoneminder.links +++ /dev/null @@ -1 +0,0 @@ -/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.linktrees b/distros/ubuntu1604/zoneminder.linktrees deleted file mode 100644 index 8a9ca2723..000000000 --- a/distros/ubuntu1604/zoneminder.linktrees +++ /dev/null @@ -1,6 +0,0 @@ -## cakephp -#replace /usr/share/php/Cake /usr/share/zoneminder/www/api/lib/Cake - -## libjs-jquery -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.lintian-overrides b/distros/ubuntu1604/zoneminder.lintian-overrides deleted file mode 100644 index 90be05a9f..000000000 --- a/distros/ubuntu1604/zoneminder.lintian-overrides +++ /dev/null @@ -1,14 +0,0 @@ -# Depends: policykit-1 -unusual-interpreter usr/bin/zmsystemctl.pl #!/usr/bin/pkexec - -# Intentionally not others-readable, #637685. -non-standard-file-perm etc/zm/zm.conf 0640 != 0644 - -# Bundled Cake PHP framework, not intended for direct execution: -script-not-executable usr/share/zoneminder/www/api/* - -# Annoying but seems to be too much troubles to fix; should be fixed upstream: -script-with-language-extension usr/bin/*.pl - -# dh-linktree: -package-contains-broken-symlink usr/share/zoneminder/www/api/lib/Cake/* diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/ubuntu1604/zoneminder.logrotate deleted file mode 100644 index 6162e9c4d..000000000 --- a/distros/ubuntu1604/zoneminder.logrotate +++ /dev/null @@ -1,13 +0,0 @@ -/var/log/zm/*.log { - missingok - notifempty - sharedscripts - delaycompress - compress - postrotate - /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : - endscript - daily - rotate 7 - maxage 7 -} diff --git a/distros/ubuntu1604/zoneminder.maintscript b/distros/ubuntu1604/zoneminder.maintscript deleted file mode 100644 index 3aa20b3a0..000000000 --- a/distros/ubuntu1604/zoneminder.maintscript +++ /dev/null @@ -1 +0,0 @@ -rm_conffile /etc/zm/apache.conf 1.28.1-5~ diff --git a/distros/ubuntu1604/zoneminder.manpages b/distros/ubuntu1604/zoneminder.manpages deleted file mode 100644 index d2053d688..000000000 --- a/distros/ubuntu1604/zoneminder.manpages +++ /dev/null @@ -1 +0,0 @@ -docs/_build/man/*.1 diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst deleted file mode 100644 index e51abb5a9..000000000 --- a/distros/ubuntu1604/zoneminder.postinst +++ /dev/null @@ -1,93 +0,0 @@ -#! /bin/sh - -set +e - -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 - 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 - fi - if [ ! -e "/etc/apache2/mods-enabled/rewrite.load" ] && [ "$(command -v a2enmod)" != "" ]; then - echo "The rewrite module is not enabled in apache2. I am enabling it using a2enmod rewrite." - a2enmod rewrite - 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 $? - - # - # Get mysql started if it isn't running - # - - if [ -e "/lib/systemd/system/mariadb.service" ]; then - DBSERVICE="mariadb.service" - else - DBSERVICE="mysql.service" - fi - echo "Detected db service is $DBSERVICE" - if systemctl is-failed --quiet $DBSERVICE; then - echo "$DBSERVICE is in a failed state; it will not be started." - echo "If you have already resolved the problem preventing $DBSERVICE from running," - 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}'@localhost IDENTIFIED BY '${ZM_DB_PASS}';" | mysql --defaults-file=/etc/mysql/debian.cnf mysql - fi - 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 --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." - 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 - - echo "Done Updating; starting ZoneMinder." - deb-systemd-invoke restart zoneminder.service - -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.postrm b/distros/ubuntu1604/zoneminder.postrm deleted file mode 100644 index ba2066c8d..000000000 --- a/distros/ubuntu1604/zoneminder.postrm +++ /dev/null @@ -1,14 +0,0 @@ -#! /bin/sh - -set -e - -if [ "$1" = "purge" ]; then - echo " -Reminder: to completely remove \"zoneminder\" it may be necessary - * to delete database using the following sample command: - sudo mysqladmin --defaults-file=/etc/mysql/debian.cnf -f drop zm - * to delete remaining data files in "/var/cache/zoneminder". -" -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.preinst b/distros/ubuntu1604/zoneminder.preinst deleted file mode 100644 index 6088c3ea9..000000000 --- a/distros/ubuntu1604/zoneminder.preinst +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -e - -## Remove obsolete symlink which is in the way of dh_apache2: -ol="/etc/apache2/conf-available/zoneminder.conf" -if [ -h "${ol}" ]; then - [ "$(readlink ${ol})" = "/etc/zm/apache.conf" ] && rm -f "${ol}" -fi - -#DEBHELPER# diff --git a/distros/ubuntu1604/zoneminder.service b/distros/ubuntu1604/zoneminder.service deleted file mode 100644 index cb2d6791e..000000000 --- a/distros/ubuntu1604/zoneminder.service +++ /dev/null @@ -1,23 +0,0 @@ -# ZoneMinder systemd unit file -# This file is intended to work with Debian distributions - -[Unit] -Description=ZoneMinder CCTV recording and surveillance system -After=network.target mysql.service -# Remarked out so that it will start ZM on machines that don't have mysql installed -#Requires=mysql.service - -[Service] -#User=www-data -Type=forking -ExecStart=/usr/bin/zmpkg.pl start -ExecReload=/usr/bin/zmpkg.pl restart -ExecStop=/usr/bin/zmpkg.pl stop -PIDFile=/run/zm/zm.pid -Restart=always -RestartSec=10 -Environment=TZ=:/etc/localtime -TimeoutSec=600 - -[Install] -WantedBy=multi-user.target diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index e02ff0e14..43c8853f1 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -2,9 +2,8 @@ Source: zoneminder Section: net Priority: optional Maintainer: Isaac Connor -Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 +Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-apache2 ,cmake - ,libavdevice-dev ,libavcodec-dev ,libavformat-dev ,libavutil-dev @@ -13,7 +12,6 @@ Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,ffmpeg ,net-tools ,libbz2-dev - ,libgcrypt20-dev ,libcurl4-gnutls-dev ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev ,libturbojpeg0-dev @@ -33,15 +31,18 @@ Build-Depends: debhelper (>= 12), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,libdata-entropy-perl ,libvncserver-dev ,libfmt-dev + ,libjwt-gnutls-dev|libjwt-dev + Standards-Version: 4.5.0 Homepage: https://www.zoneminder.com/ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} + ,sudo ,javascript-common - ,libswscale5 - ,libswresample3 + ,libswscale5|libswscale4 + ,libswresample3|libswresample2 ,ffmpeg ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl @@ -76,6 +77,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libdata-entropy-perl ,libvncclient1|libvncclient0 ,libfmt + ,libjwt-gnutls0|libjwt0 + Recommends: ${misc:Recommends} ,libapache2-mod-php | php-fpm ,default-mysql-server | mariadb-server | virtual-mysql-server diff --git a/distros/ubuntu2004/copyright b/distros/ubuntu2004/copyright index 64189b0f4..eb5274cbf 100644 --- a/distros/ubuntu2004/copyright +++ b/distros/ubuntu2004/copyright @@ -41,7 +41,6 @@ Copyright: 2005-2013 Cake Software Foundation, Inc. (http://cakefoundation.org) License: Expat Files: - cmake/Modules/CheckPrototypeDefinition*.cmake cmake/Modules/FindGLIB2.cmake cmake/Modules/FindPolkit.cmake cmake/Modules/GNUInstallDirs.cmake diff --git a/distros/ubuntu2004/zoneminder.postinst b/distros/ubuntu2004/zoneminder.postinst index b0fd254f5..6f26bca24 100644 --- a/distros/ubuntu2004/zoneminder.postinst +++ b/distros/ubuntu2004/zoneminder.postinst @@ -5,6 +5,12 @@ set +e create_db () { echo "Checking for db" mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload + if [ $? -ne 0 ]; then + echo "Cannot talk to database. You will have to create the db manually with something like:"; + echo "cat /usr/share/zoneminder/db/zm_create.sql | mysql -u root"; + return; + fi + # 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" @@ -31,8 +37,16 @@ create_update_user () { update_db () { - 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 diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu2004/zoneminder.tmpfile similarity index 73% rename from distros/ubuntu1604/zoneminder.tmpfile rename to distros/ubuntu2004/zoneminder.tmpfile index cbfdec1de..a92ad2627 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu2004/zoneminder.tmpfile @@ -1,4 +1,4 @@ d /run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data -d /var/tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data 7d d /var/cache/zoneminder/cache 0755 www-data www-data diff --git a/distros/ubuntu2004/zoneminder.tmpfiles b/distros/ubuntu2004/zoneminder.tmpfiles deleted file mode 100644 index cbfdec1de..000000000 --- a/distros/ubuntu2004/zoneminder.tmpfiles +++ /dev/null @@ -1,4 +0,0 @@ -d /run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data -d /var/tmp/zm 0755 www-data www-data -d /var/cache/zoneminder/cache 0755 www-data www-data diff --git a/docs/faq.rst b/docs/faq.rst index 1db30e05f..f23fa2fc1 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -11,18 +11,21 @@ How can I stop ZoneMinder filling up my disk? --------------------------------------------- Recent versions of ZoneMinder come with a filter you can use for this purpose already included. -The filter is called **PurgeWhenFull** and to find it, choose one of the event counts from the console page, for instance events in the last hour, for one of your monitors. **Note** that this filter is automatically enabled if you do a fresh install of ZoneMinder including creating a new database. If you already have an existing database and are upgrading ZoneMinder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. +The filter is called **PurgeWhenFull** and to find it, click on the word **Filters** in the header. +**Note** that this filter is automatically enabled if you do a fresh install of ZoneMinder including creating a new database. If you already have an existing database and are upgrading ZoneMinder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. -To enable it, go to Web Console, click on any of your Events of any of your monitors. -This will bring up an event listing and a filter window. +To enable it, go to Web Console, click on the word **Filters** in the UI header. -In the filter window there is a drop down select box labeled 'Use Filter', that lets your select a saved filter. Select 'PurgeWhenFull' and it will load that filter. +In the filter window there is a drop down select box labeled 'Use Filter', that lets you select a saved filter. Select 'PurgeWhenFull' and it will load that filter. Make any modifications you might want, such as the percentage full you want it to kick in, or how many events to delete at a time (it will repeat the filter as many times as needed to clear the space, but will only delete this many events each time to get there). -Then click on 'Save' which will bring up a new window. Make sure the 'Automatically delete' box is checked and press save to save your filter. This will then run in the background to keep your disk within those limits. +Ensure that the Run filter in background checkbox is checked. +Ensure that the Delete all matches checkbox is checked. -After you've done that, you changes will automatically be loaded into zmfilter within a few minutes. +Then click on 'Save'. The filter will immediately begin executing in the background to keep your disk within those limits. + +Please note that that this filter will only affect the default storage location. If you have added other storage areas, you must create a PurgeWhenFull filter for each one, and specify the Storage Area as one of the parameters in the filter. You can duplicate the existing PurgeWhenFull filter by using Save As instead of Save. 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. @@ -46,7 +49,7 @@ Normally an event created as the result of an alarm consists of entries in one o ZM_RUN_AUDIT: -The zmaudit daemon exists to check that the saved information in the database and on the file system match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronize the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. This is recommended for most systems however if you have a very large number of events the process of scanning the database and file system may take a long time and impact performance. In this case you may prefer to not have zmaudit running unconditionally and schedule occasional checks at other, more convenient, times. +The zmaudit daemon exists to check that the saved information in the database and on the file system match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronize the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. This is not recommended for most systems, as zmaudit.pl is very resource intensive. ZM_AUDIT_CHECK_INTERVAL: @@ -184,14 +187,27 @@ Once I did this, images started to stream for me. Lastly, please look for errors created by the zmc processes. If zmc isn't running, then zms will not be able to get an image from it and will exit. -I have several monitors configured but when I load the Montage view in FireFox why can I only see two? or, Why don't all my cameras display when I use the Montage view in FireFox? +I have several monitors configured but when I load the Montage view why can I only see two? or, Why don't all my cameras display when I use the Montage view? -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -By default FireFox only supports a small number of simultaneous connections. Using the montage view usually requires one persistent connection for each camera plus intermittent connections for other information such as statuses. +By default most browsers only support a small number of simultaneous connections to any given server. Using the montage view usually requires one persistent connection for each camera plus intermittent connections for other information such as statuses. -You will need to increase the number of allowed connections to use the montage view with more than a small number of cameras. Certain FireFox extensions such as FasterFox may also help to achieve the same result. +In firefox you can increase the limit, but other browsers are not configurable in this way. -To resolve this situation, follow the instructions below: +A solution for all browsers is something we call multi-port. We reconfigure apache to operate on ports other than the default of 80(http) or 443(https). You need to pick a range, let's say 30000 to 30010 in order to support 10 cameras. We add lines to your zoneminder apache config file as follows: + +Listen 30000 +Listen 30001 +Listen 30002 +Listen 30003 +etc +Listen 30010 + +If you are using virtualhosts, you will have to add these to the VirtualHost directive as well. + +Then in ZoneMinder config, Go Options -> Network and set MIN_STREAMING_PORT to 30000. Now when generating urls to stream images from ZoneMinder a port will be appended that is 30000 + MonitorId, so Monitor 1 will stream from 30001 and so on. This will allow Montage to stream from all monitors. + +Alternatively if you are in fact using only Firefox, you can increase the limit as follows: Enter ``about:config`` in the address bar @@ -209,7 +225,7 @@ change the 3 to a 1 I can't see more than 6 monitors in montage on my browser --------------------------------------------------------- -Browsers such a Chrome and Safari only support upto 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` +Browsers such a Chrome and Safari only support up to 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` Why is ZoneMinder using so much CPU? --------------------------------------- diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index d09423bd9..f7325fe1f 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -3,6 +3,50 @@ Debian .. contents:: +Easy Way: Debian 11 (Bullseye) +------------------------ + +This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye). + +**Step 1:** Setup Sudo (optional but recommended) + +By default Debian does not come with sudo, so you have to install it and configure it manually. +This step is optional but recommended and the following instructions assume that you have setup sudo. +If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly. + +:: + + apt install sudo + usermod -a -G sudo + exit + +Now your terminal session is back under your normal user. You can check that +you are now part of the sudo group with the command ``groups``, "sudo" should +appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``. + +**Step 2:** Update system and install zoneminder + +Run the following commands. + +:: + + sudo apt update + sudo apt upgrade + sudo apt install mariadb-server + sudo apt install zoneminder + +When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``. + +**Step 3:** Setup permissions for zm.conf + +To make sure zoneminder can read the configuration file, run the following command. + +:: + + sudo chgrp -c www-data /etc/zm/zm.conf + +Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm`` + Easy Way: Debian Buster ------------------------ @@ -56,12 +100,13 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file :: # ZoneMinder repository - deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/ + deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/ You can do this using: -:: - echo "deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list +.. code-block:: + + echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 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. :: @@ -157,7 +202,7 @@ You are now ready to go with ZoneMinder. Open a browser and type either ``localh Easy Way: Debian Stretch ------------------------ -This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.34 on Debian 9.8. +This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.36 on Debian 9.8. **Step 1:** Make sure your system is up to date @@ -203,7 +248,7 @@ Add the following to the bottom of the file :: # ZoneMinder repository - deb https://zmrepo.zoneminder.com/debian/release-1.34 stretch/ + deb https://zmrepo.zoneminder.com/debian/release-1.36 stretch/ CTRL+o and to save CTRL+x to exit @@ -292,175 +337,3 @@ Reload Apache to enable your changes and then start ZoneMinder. sudo systemctl start zoneminder You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer. - - -Easy Way: Debian Jessie ------------------------ - -**Step 1:** Setup Sudo - -By default Debian does not come with sudo. Log in as root or use su command. -N.B. The instructions below are for setting up sudo for your current account, you can -do this as root if you prefer. - -:: - - apt-get update - apt-get install sudo - usermod -a -G sudo - exit - -Logout or try ``newgrp`` to reload user groups - -**Step 2:** Run sudo and update - -Now run session using sudo and ensure system is updated. -:: - - sudo -i - apt-get upgrade - -**Step 3:** Install Apache and MySQL - -These are not dependencies for the package as they could -be installed elsewhere. - -:: - - apt-get install apache2 mysql-server - -**Step 4:** Edit sources.list to add jessie-backports - -:: - - nano /etc/apt/sources.list - -Add the following to the bottom of the file - -:: - - # Backports repository - deb http://archive.debian.org/debian/ jessie-backports main contrib non-free - -CTRL+o and to save -CTRL+x to exit - -Run the following - -:: - - echo 'Acquire::Check-Valid-Until no;' > /etc/apt/apt.conf.d/99no-check-valid-until - -**Step 5:** Install ZoneMinder - -:: - - apt-get update - apt-get install zoneminder - -**Step 6:** Read the Readme - -The rest of the install process is covered in the README.Debian, so feel free to have -a read. - -:: - - zcat /usr/share/doc/zoneminder/README.Debian.gz - -**Step 7:** Setup Database - -Install the zm database and setup the user account. Refer to Hints in Ubuntu install -should you choose to change default database user and password. - -:: - - cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf - echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql - -**Step 8:** zm.conf Permissions - -Adjust permissions to the zm.conf file to allow web account to access it. - -:: - - chgrp -c www-data /etc/zm/zm.conf - -**Step 9:** Setup ZoneMinder service - - :: - - systemctl enable zoneminder.service - -**Step 10:** Configure Apache - -The following commands will setup the default /zm virtual directory and configure -required apache modules. - -:: - - a2enconf zoneminder - a2enmod cgi - a2enmod rewrite - -**Step 11:** 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. **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 12:** Please check the configuration - - 1. Check path of ZM_PATH in '/etc/zm/conf.d/zmcustom.conf' is ZM_PATH_ZMS=/zm/cgi-bin/nph-zms - :: - cat /etc/zm/conf.d/zmcustom.conf - - 2. Check config of /etc/apache2/conf-enabled/zoneminder.conf has the same ScriptAlias /zm/cgi-bin that is configured - in ZM_PATH. The part /nph-zms has to be left out of the ScriptAlias - - ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" - - - :: - cat /etc/apache2/conf-enabled/zoneminder.conf - -**Step 13:** Start ZoneMinder - -Reload Apache to enable your changes and then start ZoneMinder. - -:: - - systemctl reload apache2 - systemctl start zoneminder - -**Step 14:** 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.34.0", - "apiversion": "1.34.0.1" - } - -**Congratulations** Your installation is complete diff --git a/docs/installationguide/easydocker.rst b/docs/installationguide/easydocker.rst index 899ce0e5d..a7d37a9e5 100644 --- a/docs/installationguide/easydocker.rst +++ b/docs/installationguide/easydocker.rst @@ -2,7 +2,7 @@ An Easy To Use Docker Image =========================== If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories: -* If you want to run the latest stable release, please use his `zoneminder repository `__. +* If you want to run the latest stable release, please use his `zoneminder machine learning repository `__. * If you want to run the latest zoneminder master, please use his `zoneminder master repository `__. In both cases, instructions are provided in the repo README files. diff --git a/docs/installationguide/packpack.rst b/docs/installationguide/packpack.rst index 72fdf8661..64a6b4004 100644 --- a/docs/installationguide/packpack.rst +++ b/docs/installationguide/packpack.rst @@ -77,7 +77,7 @@ To start the build, simply execute the following command from the root folder of OS= DIST= utils/packpack/startpackpack.sh -Where is the name of the distro you wish to build on, such as fedora, and is release name or number of the distro you wish to build on. Redhat distros expect a number for while Debian and Ubuntu distros expect a name. For example: +Where is the name of the distro you wish to build on, such as fedora, and is the release name or number of the distro you wish to build on. Redhat distros expect a number for while Debian and Ubuntu distros expect a name. For example: :: @@ -85,7 +85,7 @@ Where is the name of the distro you wish to build on, such as fedor :: - OS=ubuntu DIST=xenial utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute utils/packpack/startpackpack.sh Once you enter the appropriate command, go get a coffee while a ZoneMinder package is built. When the build finished, you can find the resulting packages under a subfolder called "build". @@ -93,13 +93,13 @@ Note that this will build packages with x86_64 architecture. This build method c :: - OS=ubuntu DIST=xenial ARCH=i386 utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute ARCH=i386 utils/packpack/startpackpack.sh For advanced users who really want to go out into uncharted waters, it is theoretically possible to build arm packages as well, as long as the host architecture is compatible. :: - OS=ubuntu DIST=xenial ARCH=armhfp utils/packpack/startpackpack.sh + OS=ubuntu DIST=hirsute ARCH=armhfp utils/packpack/startpackpack.sh Building arm packages in this manner has not been tested by us, however. diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 7f2a01eb2..d59d58f38 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -41,7 +41,7 @@ guide you with a quick search. :: - add-apt-repository ppa:iconnor/zoneminder-1.34 + add-apt-repository ppa:iconnor/zoneminder-1.36 Update repo and upgrade. @@ -176,175 +176,6 @@ CTRL+x to exit PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ -Easy Way: Ubuntu 16.04 (Xenial) -------------------------------- -These instructions are for a brand new ubuntu 16.04 system which does not have ZM -installed. - - -It is recommended that you use an Ubuntu Server install and select the LAMP option -during install to install Apache, MySQL and PHP. If you failed to do this you can -achieve the same result by running: - -:: - - 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. - -**Step 1:** Either run commands in this install using sudo or use the below to become root -:: - - sudo -i - -**Step 2:** Update Repos - -.. topic :: Latest Release - - ZoneMinder is now part of the current standard Ubuntu repository, but - sometimes the official repository can lag behind. To find out check our - `releases page `_ for - the latest release. - - Alternatively, the ZoneMinder project team maintains a `PPA `_, which is updated immediately - following a new release of ZoneMinder. To use this repository instead of the - official Ubuntu repository, enter the following from the command line: - - :: - - add-apt-repository ppa:iconnor/zoneminder - add-apt-repository ppa:iconnor/zoneminder-1.32 - -Update repo and upgrade. - -:: - - apt-get update - apt-get upgrade - apt-get dist-upgrade - - -**Step 3:** Configure MySQL - -.. sidebar :: Note - - 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 - | /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf - | /etc/mysql/mysql.cnf is a basic file - -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 -causing problems in 1.32.0. - -To better manage the MySQL server it is recommended to copy the sample config file and -replace the default my.cnf symbolic link. - -:: - - rm /etc/mysql/my.cnf (this removes the current symbolic link) - cp /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/my.cnf - -To change MySQL settings: - -:: - - nano /etc/mysql/my.cnf - -In the [mysqld] section add the following - -:: - - sql_mode = NO_ENGINE_SUBSTITUTION - -CTRL+o then [Enter] to save - -CTRL+x to exit - -Restart MySQL - -:: - - systemctl restart mysql - - -**Step 4:** Install ZoneMinder - -:: - - apt-get install zoneminder - -**Step 5:** Configure the ZoneMinder Database - -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';" - - -**Step 6:** Set permissions - -Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content - -:: - - chmod 740 /etc/zm/zm.conf - chown root:www-data /etc/zm/zm.conf - chown -R www-data:www-data /usr/share/zoneminder/ - -**Step 7:** Configure Apache correctly: - -:: - - a2enmod cgi - a2enmod rewrite - a2enconf zoneminder - -You may also want to enable to following modules to improve caching performance - -:: - - a2enmod expires - a2enmod headers - -**Step 8:** Enable and start Zoneminder - -:: - - systemctl enable zoneminder - systemctl start zoneminder - -**Step 10:** Reload Apache service - -:: - - systemctl reload apache2 - -**Step 11:** 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.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`_ Harder Way: Build Package From Source ------------------------------------- @@ -382,7 +213,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 "xenial", "bionic" etc. You +which simply extracts your distribution name - like "bionic", "hirsute" 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 diff --git a/docs/userguide/definemonitor.rst b/docs/userguide/definemonitor.rst index a5b622872..ab58b656c 100644 --- a/docs/userguide/definemonitor.rst +++ b/docs/userguide/definemonitor.rst @@ -86,7 +86,7 @@ Source Path Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this: * Check the documentation that came with your camera - * Look for your camera in the hardware compatibilty list in the `hardware compatibility wiki `__ + * Look for your camera in the hardware compatibility list in the `hardware compatibility wiki `__ * Try ZoneMinder's new ONVIF probe feature * Download and install the `ONVIF Device Manager `__ onto a Windows machine * Use Google to find third party sites, such as ispy, which document this information @@ -179,12 +179,12 @@ Storage Tab The storage section allows for each monitor to configure if and how video and audio are recorded. Save JPEGs - Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded. + Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows one to view an event anytime while it is being recorded. * Disabled – video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all. * Frames only – video is recorded in individual JPEG frames. - * Analysis images only (if available) – video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames. - * Frames + Analysis images (if available) – video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid. + * Analysis images only (if available) – video is recorded in individual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames. + * Frames + Analysis images (if available) – video is recorded twice, once as normal individual JPEG frames and once in individual JPEG frames with analysis information overlaid. Video Writer Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored. diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index 436034da0..77f811fba 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -4,7 +4,7 @@ Filtering Events Filters allow you to define complex conditions with associated actions in ZoneMinder. Examples could include: * Send an email each time a new event occurs for a specific monitor -* Delete events that are more than 10 days old +* Delete events that are more than 10 days old And many more. @@ -29,14 +29,17 @@ Here is what the filter window looks like events later and also make sure archived events don't get deleted, for example .. todo :: - fill in what update used disk space, copy all matches, move all matches do. For the "create video" filter, put in more details on how it works, any dependencies etc. + For the "create video" filter, put in more details on how it works, any dependencies etc. - * Update used disk space: + * Update used disk space: calculates how much disk space is currently taken by the event and updates the db record. * Create video for all matches: creates a video file of all the events that match - * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. + * Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage. + * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceded by replacement arguents. eg: /usr/bin/script.sh %MN% will execute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%". * Delete all matches: Deletes all the matched events. - * Copy all matches: - * Move all matches: + * Email details of all matches: Sends an email to the configured address with details about the event. + * Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options. + * Message details of all matches: Uses an email to SMS gateway to send an SMS message for each match. + * Move all matches: copies the event files to another location, specified in the Move To dropdown. The other location must be setup in the Storage Tab under options. The files will be delete from the original location. * Run filter in background: When checked, ZoneMinder will make sure the filter is checked regularly. For example, if you want to be notified of new events by email, you should make sure this is checked. Filters that are configured to run in the background have a “*” next to it. * Run filter concurrently: Allows this filter to run in its own thread thereby letting other filters run in parallel. @@ -70,14 +73,21 @@ 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 + * %EI1A% Attach first alarmed event analysis image * %EIM% Attach (first) event image with the highest score + * %EIMA% Attach (first) event analysis 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 + * %EVM% Attach event mpeg video in phone format * %MN% Name of the monitor * %MET% Total number of events for the monitor * %MEH% Number of events for the monitor in the last hour @@ -107,12 +117,11 @@ How filters actually work -------------------------- It is useful to know how filters actually work behind the scenes in ZoneMinder, in the event you find your filter not functioning as intended: -* the primary filter processing process in ZoneMinder is a perl file called ``zmfilter.pl`` which retrieves filters from the Filters database table +* Each filter set to run in the background will be run in it's own process called ``zmfilter.pl`` which retrieves filters from the Filters database table * zmfilter.pl runs every FILTER_EXECUTE_INTERVAL seconds (default is 20s, can be changed in Options->System) -* in each run, it goes through all the filters which are marked as "Run in Background" and if the conditions match performs the specified action -* zmfilter.pl also reloads all the filters every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System) - * So if you have just created a new filter, zmfilter will not see it till the next FILTER_RELOAD_DELAY cycle - * This is also important if you are using "relative times" like 'now' - see :ref:`relative_caveat` +* after each interval the filter will query the database and apply the action to each matching event. +* zmfilter.pl also reloads the filter every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System) +* In previous versions of ZoneMinder filter changes would not take immediate effect, but now the web ui will start/stop/restart filters as appropriate upon editing a filter. Relative items in date strings diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 99f2ea4ba..61d4cb523 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -53,7 +53,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of * **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 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. +* **E**: The Cycle option allows you to rotate between live views of each configured monitor. * **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around. * **G**: Montage Review allows you to simultaneously view past events for different monitors. Note that this is a very resource intensive page and its performance will vary based on your system capabilities. * **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files. diff --git a/docs/userguide/mobile.rst b/docs/userguide/mobile.rst index c6da91642..7f000bea3 100644 --- a/docs/userguide/mobile.rst +++ b/docs/userguide/mobile.rst @@ -6,7 +6,7 @@ Here are some options for using ZoneMinder on Mobile devices: Third party mobile clients ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * zmNinja (`source code `__, needs APIs to be installed to work) - * Available in App Store, Play Store and for Desktops - `website `__ + * Available in App Store, Play Store and for Desktops - `website `__ Using the existing web console ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,4 +17,4 @@ Discontinued clients The following are a list of clients that do not work and have not been updated: * eyeZM -* zmView \ No newline at end of file +* zmView diff --git a/docs/userguide/options/options_email.rst b/docs/userguide/options/options_email.rst index eef35bfa2..84c1596f4 100644 --- a/docs/userguide/options/options_email.rst +++ b/docs/userguide/options/options_email.rst @@ -101,6 +101,6 @@ FROM_EMAIL - The emails or messages that will be sent to you informing you of ev URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``. -SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page `__ for setup and configuration help. +SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page `__ for setup and configuration help. -SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. \ No newline at end of file +SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. diff --git a/docs/userguide/options/options_storage.rst b/docs/userguide/options/options_storage.rst index 623ad50d5..73dfb574a 100644 --- a/docs/userguide/options/options_storage.rst +++ b/docs/userguide/options/options_storage.rst @@ -27,6 +27,7 @@ S3 storage setup You must use s3fs to mount the S3 bucket in your fs tree. Telling ZoneMinder that the location is S3 will let it use more efficient code to send and delete the event data. +The Do Deletes option tells ZoneMinder whether to actually perform delete operations when deleting events. S3fs systems often do deletes in a cron job or other background task and doing the deletes can overload an S3 system. Refer to this guide for installation and configuration of s3fs - https://github.com/s3fs-fuse/s3fs-fuse diff --git a/docs/userguide/options/options_users.rst b/docs/userguide/options/options_users.rst index def8744d7..4258c4ed0 100644 --- a/docs/userguide/options/options_users.rst +++ b/docs/userguide/options/options_users.rst @@ -30,7 +30,7 @@ This screen allows you to configure various permissions on a per user basis. The .. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission - Bandwidth - - Specifies the maximum bandwith that this user can configure (Low, Medium or High) + - Specifies the maximum bandwidth that this user can configure (Low, Medium or High) - API enabled - Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja) @@ -42,4 +42,4 @@ Here is an example of a restricted user, for example: .. image:: images/Options_Users_Example.png -This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ. \ No newline at end of file +This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ. diff --git a/docs/userguide/options/options_web.rst b/docs/userguide/options/options_web.rst index 6766a2248..7ae0ac628 100644 --- a/docs/userguide/options/options_web.rst +++ b/docs/userguide/options/options_web.rst @@ -5,10 +5,7 @@ This screen lets you customize several aspects of the web interface of ZoneMinde .. image:: images/Options_web.png -WEB_TITLE - - -.. todo :: - not quite sure what this does. Seems to change the "target" name - not sure what effect it is supposed to have. +WEB_TITLE - The actual text that is shown on the login screen. It is possible that it also appears in other areas. WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. @@ -48,4 +45,4 @@ WEB_USE_OBJECT_TAGS - There are two methods of including media content in web pa WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off -WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. \ No newline at end of file +WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. diff --git a/fonts/default.zmfnt b/fonts/default.zmfnt index e74a798a0..e13998a39 100644 Binary files a/fonts/default.zmfnt and b/fonts/default.zmfnt differ diff --git a/misc/fail2ban.rules b/misc/fail2ban.rules new file mode 100644 index 000000000..c1d5115fd --- /dev/null +++ b/misc/fail2ban.rules @@ -0,0 +1,2 @@ +failregex = ^\s*web_php\[\d+\]\.ERR \[\].*includes/auth.php +datepattern = ^%%m/%%d/%%y %%H:%%M:%%S(?:\.%%f) diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm index 89de61e18..b6a5a487a 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L object. @@ -3093,7 +3093,7 @@ Returns a L object. @@ -3288,7 +3288,7 @@ Returns a L object. @@ -3298,7 +3298,7 @@ Returns a L object. diff --git a/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm index a4bb4696b..87c17a350 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L object. diff --git a/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm index 12e921a67..f6bdde62a 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm index 687131675..e88c29862 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm index d1c5faa29..950c26344 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm @@ -987,7 +987,7 @@ Returns a L object. diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm index e69cc37ad..e827b659b 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm index 8f68d1ef5..825f45ffa 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/MotionInCells.pm @@ -147,7 +147,7 @@ This attribute is of type L or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm b/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm index cd183e240..d89688025 100644 --- a/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm +++ b/onvif/proxy/lib/WSDiscovery10/Types/FaultCodeOpenType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm b/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm index 45aab20e5..84f5985f8 100644 --- a/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm +++ b/onvif/proxy/lib/WSDiscovery10/Types/OpenRelationshipType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm index a7dcd3532..0aa2a393c 100644 --- a/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm +++ b/onvif/proxy/lib/WSDiscovery11/Interfaces/WSDiscovery/WSDiscoveryPort.pm @@ -100,7 +100,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm index 52f5d2c8f..42fe97b83 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodeOpenType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm index 89d8704e8..072948d96 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm index dc3e004c2..31c9df482 100644 --- a/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm +++ b/onvif/proxy/lib/WSDiscovery11/Types/RelationshipTypeOpenEnum.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm b/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm index ac8dc805d..c80f98fe0 100644 --- a/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm +++ b/onvif/proxy/lib/WSNotification/Types/AbsoluteOrRelativeTimeType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm b/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm index 06e4df192..fae356a95 100644 --- a/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm +++ b/onvif/proxy/lib/WSNotification/Types/FaultCodesOpenEnumType.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm b/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm index e598093cb..0125e612c 100644 --- a/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm +++ b/onvif/proxy/lib/WSNotification/Types/RelationshipTypeOpenEnum.pm @@ -44,7 +44,7 @@ not checked yet. The current implementation of union resorts to inheriting from the base type, which means (quoted from the XML Schema specs): "If the or -alternative is chosen, then the simple ur-type definition·." +alternative is chosen, then the simple ur-type definition." diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 3a07a60d9..48d494614 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 @@ -308,7 +308,7 @@ our @options = ( }, { name => 'ZM_AUTH_HASH_IPS', - default => 'yes', + default => 'no', description => 'Include IP addresses in the authentication hash', help => q` When ZoneMinder is running in hashed authenticated mode it can @@ -346,7 +346,7 @@ our @options = ( }, { name => 'ZM_AUTH_HASH_LOGINS', - default => 'no', + default => 'yes', description => 'Allow login by authentication hash', help => q` The normal process for logging into ZoneMinder is via the login @@ -508,7 +508,7 @@ our @options = ( }, { name => 'ZM_SYSTEM_SHUTDOWN', - default => 'true', + default => 'no', 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~~ ~~ @@ -518,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', @@ -1056,7 +1064,7 @@ our @options = ( }, { name => 'ZM_FFMPEG_FORMATS', - default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + default => 'mp4* mpg mpeg wmv asf avi mov swf 3gp**', description => 'Formats to allow for ffmpeg video generation', help => q` Ffmpeg can generate video in many different formats. This @@ -1088,7 +1096,7 @@ our @options = ( }, { name => 'ZM_LOG_LEVEL_SYSLOG', - default => '0', + default => '-1', description => 'Save logging output to the system log', help => q` ZoneMinder logging is now more integrated between @@ -1596,7 +1604,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_DISK_SPACE', - default => 'no', + default => 'yes', description => 'Whether to show disk space used by each event.', help => q` Adds another column to the listing of events @@ -1626,7 +1634,7 @@ our @options = ( }, { name => 'ZM_WEB_ID_ON_CONSOLE', - default => 'no', + default => 'yes', description => 'Should the console list the monitor id', help => q` Some find it useful to have the id always visible @@ -2262,7 +2270,7 @@ our @options = ( }, { name => 'ZM_MAX_RESTART_DELAY', - default => '600', + default => '30', description => 'Maximum delay (in seconds) for daemon restart attempts.', help => q` The zmdc (zm daemon control) process controls when processeses @@ -2661,7 +2669,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_SORT_FIELD', - default => 'StartTime', + default => 'StartDateTime', description => 'Default field the event lists are sorted by', help => q` Events in lists can be initially ordered in any way you want. @@ -2673,7 +2681,7 @@ our @options = ( `, type => { db_type =>'string', - hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + hint =>'Id|Name|Cause|DiskSpace|MonitorName|StartDateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', pattern =>qr|.|, format =>q( $1 ) }, @@ -2847,7 +2855,7 @@ our @options = ( }, { name => 'ZM_WEB_H_REFRESH_MAIN', - default => '60', + default => '240', introduction => q` There are now a number of options that are grouped into bandwidth categories, this allows you to configure the @@ -3105,7 +3113,7 @@ our @options = ( }, { name => 'ZM_WEB_H_AJAX_TIMEOUT', - default => '3000', + default => '10000', description => 'How long to wait for Ajax request responses (ms)', help => q` The newer versions of the live feed and event views use Ajax to @@ -3758,7 +3766,7 @@ our @options = ( SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. - Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder) + Please visit the ZoneMinder [SSMTP Wiki page](https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder) for setup and configuration help. `, type => $types{boolean}, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index acce8eb4a..e4d052700 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -29,6 +29,8 @@ use strict; use warnings; require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Monitor; our $VERSION = $ZoneMinder::Base::VERSION; @@ -41,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Controls'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + Type + Protocol + CanWake + CanSleep + CanReset + CanReboot + CanZoom + CanAutoZoom + CanZoomAbs + CanZoomRel + CanZoomCon + MinZoomRange + MaxZoomRange + MinZoomStep + MaxZoomStep + HasZoomSpeed + MinZoomSpeed + MaxZoomSpeed + CanFocus + CanAutoFocus + CanFocusAbs + CanFocusRel + CanFocusCon + MinFocusRange + MaxFocusRange + MinFocusStep + MaxFocusStep + HasFocusSpeed + MinFocusSpeed + MaxFocusSpeed + CanIris + CanAutoIris + CanIrisAbs + CanIrisRel + CanIrisCon + MinIrisRange + MaxIrisRange + MinIrisStep + MaxIrisStep + HasIrisSpeed + MinIrisSpeed + MaxIrisSpeed + CanGain + CanAutoGain + CanGainAbs + CanGainRel + CanGainCon + MinGainRange + MaxGainRange + MinGainStep + MaxGainStep + HasGainSpeed + MinGainSpeed + MaxGainSpeed + CanWhite + CanAutoWhite + CanWhiteAbs + CanWhiteRel + CanWhiteCon + MinWhiteRange + MaxWhiteRange + MinWhiteStep + MaxWhiteStep + HasWhiteSpeed + MinWhiteSpeed + MaxWhiteSpeed + HasPresets + NumPresets + HasHomePreset + CanSetPresets + CanMove + CanMoveDiag + CanMoveMap + CanMoveAbs + CanMoveRel + CanMoveCon + CanPan + MinPanRange + MaxPanRange + MinPanStep + MaxPanStep + HasPanSpeed + MinPanSpeed + MaxPanSpeed + HasTurboPan + TurboPanSpeed + CanTilt + MinTiltRange + MaxTiltRange + MinTiltStep + MaxTiltStep + HasTiltSpeed + MinTiltSpeed + MaxTiltSpeed + HasTurboTilt + TurboTiltSpeed + CanAutoScan + NumScanPaths + ); + our $AUTOLOAD; -sub new { - my $class = shift; - my $id = shift; - if ( !defined($id) ) { - Fatal('No monitor defined when invoking protocol '.$class); - } - my $self = {}; - $self->{name} = $class; - $self->{id} = $id; - bless($self, $class); - return $self; -} - -sub DESTROY { -} - sub AUTOLOAD { my $self = shift; my $class = ref($self); @@ -78,25 +172,25 @@ sub AUTOLOAD { sub getKey { my $self = shift; - return $self->{id}; + return $self->{Id}; } sub open { my $self = shift; - Fatal('No open method defined for protocol '.$self->{name}); + Fatal('No open method defined for protocol '.$self->{Protocol}); } sub close { my $self = shift; $self->{state} = 'closed'; - Debug('No close method defined for protocol '.$self->{name}); + Debug('No close method defined for protocol '.$self->{Protocol}); } 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->{MonitorId})) ) { + 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..b95ac9e66 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=1&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=1&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=1&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=1&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/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm index 2b2cf3fd2..dd98b828e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -283,7 +283,7 @@ None by default. =head1 SEE ALSO See if there are better instructions for the DCS-5020L at -http://www.zoneminder.com/wiki/index.php/Dlink +https://wiki.zoneminder.com/Dlink =head1 AUTHOR diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm index 521409e11..4f05cd3da 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Floureon.pm @@ -283,7 +283,7 @@ sub presetSet my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0"; + my $cmd = 'form/presetSet?flag=3&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } @@ -294,7 +294,7 @@ sub presetGoto my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1"; + my $cmd = 'form/presetSet?flag=4&existFlag=1&language=cn&presetNum='.$preset; $self->sendCmd( $cmd ); } 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/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/Vivotek_ePTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm index 58ebe4c63..bcf1905c5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm @@ -41,120 +41,133 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); +use ZoneMinder::General qw(:all); use Time::HiRes qw( usleep ); +use URI::Encode qw(uri_encode); -sub open -{ - my $self = shift; +our $REALM = ''; +our $PROTOCOL = 'http://'; +our $USERNAME = 'admin'; +our $PASSWORD = ''; +our $ADDRESS = ''; +our $BASE_URL = ''; - $self->loadMonitor(); - Debug( "Camera open" ); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); +sub open { + my $self = shift; + $self->loadMonitor(); - $self->{state} = 'open'; + if (($self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/)) { + $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; + $USERNAME = $+{USERNAME} if $+{USERNAME}; + $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; + $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; + } else { + Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); + $ADDRESS = $self->{Monitor}->{ControlAddress}; + } + if ( !($ADDRESS =~ /:/) ) { + Error('You generally need to also specify the port. I will append :80'); + $ADDRESS .= ':80'; + } + $BASE_URL = $PROTOCOL.($USERNAME?$USERNAME.':'.$PASSWORD.'@':'').$ADDRESS; + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( 'ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $msg = shift; - my $msg_len = length($msg); +sub sendCmd { + my ($self, $cmd, $speedcmd) = @_; - Debug( $msg."[".$msg_len."]" ); + $self->printMsg( $speedcmd, 'Tx' ); + $self->printMsg( $cmd, 'Tx' ); + + my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd"); + my $res = $self->{ua}->request($req); + + if (!$res->is_success) { + Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')'); + } + return $res->is_success; } -sub sendCmd -{ - my ($self, $cmd, $speedcmd) = @_; - - my $result = undef; - - printMsg( $speedcmd, "Tx" ); - printMsg( $cmd, "Tx" ); - - my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" ); - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" ); - } - - return( $result ); +sub moveConUp { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=up', $speed ); } -sub moveConUp -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Up" ); - $self->sendCmd( 'move=up', $speed ); +sub moveConDown { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=down', $speed ); } -sub moveConDown -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Down" ); - $self->sendCmd( 'move=down', $speed ); +sub moveConLeft { + my ($self, $params) = @_; + my $speed = 'speedpan=-' . $params->{panspeed}; + $self->sendCmd( 'move=left', $speed ); } -sub moveConLeft -{ - my ($self, $params) = @_; - my $speed = 'speedpan=-' . $params->{panspeed}; - Debug( "Move Left" ); - $self->sendCmd( 'move=left', $speed ); +sub moveConRight { + my ($self, $params) = @_; + my $speed = 'speedpan=' . ($params->{panspeed} - 6); + $self->sendCmd( 'move=right', $speed ); } -sub moveConRight -{ - my ($self, $params) = @_; - my $speed = 'speedpan=' . ($params->{panspeed} - 6); - Debug( "Move Right" ); - $self->sendCmd( 'move=right', $speed ); +sub moveStop { + my $self = shift; + Debug( "Move Stop: not implemented" ); + # not implemented } -sub moveStop -{ - my $self = shift; - Debug( "Move Stop" ); - # not implemented +sub zoomConTele { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=tele', $speed ); } -sub zoomConTele -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom In" ); - $self->sendCmd( 'zoom=tele', $speed ); +sub zoomConWide { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=wide', $speed ); } -sub zoomConWide -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom Out" ); - $self->sendCmd( 'zoom=wide', $speed ); +sub reset { + my $self = shift; + $self->sendCmd( 'move=home' ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - $self->sendCmd( 'move=home' ); +sub get_config { + my $self = shift; + + my $url = $BASE_URL.'/cgi-bin/admin/lsctrl.cgi?cmd=queryStatus&retType=javascript'; + my $req = new HTTP::Request(GET => $url); + my $response = $self->{ua}->request($req); + if ( $response->is_success() ) { + my $resp = $response->decoded_content; + return ZoneMinder::General::parseNameEqualsValueToHash($resp); + } + Warn("Failed to get config from $url: " . $response->status_line()); + return; +} # end sub get_config + +sub set_config { + my $self = shift; + my $diff = shift; + + my $url = $BASE_URL.'/cgi-bin/'.$USERNAME.'/setparam.cgi?'. + join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff); + my $response = $self->{ua}->get($url); + Debug($response->content); + return $response->is_success(); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm index 8c1f2114e..c8cf84712 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 +# 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,141 @@ 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; + + my $msg = ' + '. + ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . + $msg_body . ' + '; + $self->printMsg($cmd, 'Tx'); - my $req = HTTP::Request->new(GET=>'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); + 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 $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 +235,489 @@ 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 ) { + my $iszoom = shift; + + if ( $autostop ) { Debug('Auto Stop'); + my $cmd = $controlUri; + my $msg_body; + if( $iszoom) { + $msg_body = ' + + + '.$profileToken.' + + false + + + true + + + '; + } else { + $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 eq 'move' ) { + $msg_move_body = ' + + + '.$profileToken.' + + + + + '; + + } elsif ( $type eq '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},0); } + #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},0); } #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},0); } #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},0); } #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},1); } #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},1); } +sub zoomStop { + Debug('Zoom Stop'); + my $self = shift; + my $cmd = $controlUri; + $self->autoStop($self->{Monitor}->{AutoStopTimeout},1); + Error('Zoom Stop not implemented'); +} + + + #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},0); } #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},0); } #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},0); } #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},0); } #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 + true + + '; + 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 3fdcab5e2..35d2dc6fa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -107,6 +107,7 @@ sub zmDbConnect { .$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '') , $ZoneMinder::Config::Config{ZM_DB_USER} , $ZoneMinder::Config::Config{ZM_DB_PASS} + , { mysql_enable_utf8 => 1, } ); }; if ( !$dbh or $@ ) { @@ -266,15 +267,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 48544a911..0276af097 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -584,6 +584,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } +# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date. sub CopyTo { my ( $self, $NewStorage ) = @_; @@ -614,16 +615,12 @@ sub CopyTo { Debug("$NewPath is good"); } - $ZoneMinder::Database::dbh->begin_work(); - $self->lock_and_load(); # data is reloaded, so need to check that the move hasn't already happened. if ( $$self{StorageId} == $$NewStorage{Id} ) { - $ZoneMinder::Database::dbh->commit(); return 'Event has already been moved by someone else.'; } if ( $$OldStorage{Id} != $$self{StorageId} ) { - $ZoneMinder::Database::dbh->commit(); return 'Old Storage path changed, Event has moved somewhere else.'; } @@ -661,39 +658,21 @@ sub CopyTo { } my $event_path = $subpath.$self->RelativePath(); - if ( 0 ) { # Not neccessary - Debug("Making directory $event_path/"); - if ( !$bucket->add_key($event_path.'/', '') ) { - Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr()); - } - } my @files = glob("$OldPath/*"); Debug("Files to move @files"); - foreach my $file ( @files ) { + foreach my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! $size ) { + if (!$size) { Info('Not moving file with 0 size'); } - if ( 0 ) { - my $file_contents = File::Slurp::read_file($file); - if ( ! $file_contents ) { - die 'Loaded empty file, but it had a size. Giving up'; - } - - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key($filename, $file_contents) ) { - die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr; - } - } else { - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key_filename($filename, $file) ) { - die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; - } + my $filename = $event_path.'/'.File::Basename::basename($file); + if (!$bucket->add_key_filename($filename, $file)) { + die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; } my $duration = tv_interval($starttime); @@ -704,16 +683,15 @@ sub CopyTo { }; Error($@) if $@; } else { - Error("Unable to parse S3 Url into it's component parts."); + Error('Unable to parse S3 Url into it\'s component parts.'); } - #die $@ if $@; } # end if Url } # end if s3 my $error = ''; - if ( !$moved ) { + if (!$moved) { File::Path::make_path($NewPath, {error => \my $err}); - if ( @$err ) { + if (@$err) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; @@ -724,23 +702,16 @@ sub CopyTo { } } } - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error if $error; my @files = glob("$OldPath/*"); - if ( ! @files ) { - $ZoneMinder::Database::dbh->commit(); - return 'No files to move.'; - } + return 'No files to move.' if !@files; for my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; - Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! File::Copy::copy( $file, $NewPath ) ) { + if (!File::Copy::copy($file, $NewPath)) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } @@ -749,20 +720,21 @@ sub CopyTo { } # end foreach file. } # end if ! moved - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error; } # end sub CopyTo sub MoveTo { - my ( $self, $NewStorage ) = @_; + my ($self, $NewStorage) = @_; - if ( !$self->canEdit() ) { + if (!$self->canEdit()) { Warning('No permission to move event.'); return 'No permission to move event.'; } + my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit}; + $ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction; + $self->lock_and_load(); # The fact that we are in a transaction might not imply locking + my $OldStorage = $self->Storage(undef); my $error = $self->CopyTo($NewStorage); @@ -772,11 +744,11 @@ sub MoveTo { $$self{StorageId} = $$NewStorage{Id}; $self->Storage($NewStorage); $error .= $self->save(); - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } - $ZoneMinder::Database::dbh->commit(); + + # Going to leave it to upper layer as to whether we rollback or not + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; + return $error if $error; + $self->delete_files($OldStorage); return $error; } # end sub MoveTo diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm new file mode 100644 index 000000000..0086b5bac --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm @@ -0,0 +1,99 @@ +# ========================================================================== +# +# ZoneMinder Event_Summary 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::Event_Summary; + +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 = 'Event_Summaries'; +$serial = $primary_key = 'MonitorId'; +%fields = map { $_ => $_ } qw( + MonitorId + TotalEvents + TotalEventDiskSpace + HourEvents + HourEventDiskSpace + DayEvents + DayEventDiskSpace + WeekEvents + WeekEventDiskSpace + MonthEvents + MonthEventDiskSpace + ArchivedEvents + ArchivedEventDiskSpace + ); + +%defaults = ( + 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::Event_Summary - Perl Class for Event Summaries + +=head1 SYNOPSIS + +use ZoneMinder::Event_Summary; + +=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/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 84f51a493..3a3308938 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -127,9 +127,11 @@ sub Execute { foreach my $term ( @{$$self{PostSQLConditions}} ) { if ( $$term{attr} eq 'ExistsInFileSystem' ) { foreach my $row ( @results ) { - my $event = new ZoneMinder::Event($row); + my $event = new ZoneMinder::Event($$row{Id}, $row); if ( -e $event->Path() ) { - push @filtered_events, $row; + push @filtered_events, $row if $$term{val} eq 'true'; + } else { + push @filtered_events, $row if $$term{val} eq 'false'; } } } @@ -158,143 +160,148 @@ sub Sql { # 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}).' '; } + if (!$term->{attr}) { + Error("Invalid term in filter $$self{Id}. Empty attr"); + next; + } + my $value = $term->{val}; my @value_list; - if ( $term->{attr} ) { - 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} .= '(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.StartDateTime'; - } elsif ( $term->{attr} eq 'Date' ) { - $self->{Sql} .= 'to_days( E.StartDateTime )'; - } elsif ( $term->{attr} eq 'StartDate' ) { - $self->{Sql} .= 'to_days( E.StartDateTime )'; - } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { - $self->{Sql} .= 'extract( hour_second from E.StartDateTime )'; - } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { - $self->{Sql} .= 'weekday( E.StartDateTime )'; -# EndTIme options - } elsif ( $term->{attr} eq 'EndDateTime' ) { - $self->{Sql} .= 'E.EndDateTime'; - } elsif ( $term->{attr} eq 'EndDate' ) { - $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.EndDateTime )"; - } elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { - push @{$self->{PostSQLConditions}}, $term; - $self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; - } elsif ( $term->{attr} eq 'DiskPercent' ) { - $self->{Sql} .= 'zmDiskPercent'; - $self->{HasDiskPercent} = !undef; - } elsif ( $term->{attr} eq 'DiskBlocks' ) { - $self->{Sql} .= 'zmDiskBlocks'; - $self->{HasDiskBlocks} = !undef; - } elsif ( $term->{attr} eq 'SystemLoad' ) { - $self->{Sql} .= 'zmSystemLoad'; - $self->{HasSystemLoad} = !undef; - } else { - $self->{Sql} .= 'E.'.$term->{attr}; - } + 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} .= '(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.StartDateTime'; + } elsif ( $term->{attr} eq 'Date' ) { + $self->{Sql} .= 'to_days( E.StartDateTime )'; + } elsif ( $term->{attr} eq 'StartDate' ) { + $self->{Sql} .= 'to_days( E.StartDateTime )'; + } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { + $self->{Sql} .= 'extract( hour_second from E.StartDateTime )'; + } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { + $self->{Sql} .= 'weekday( E.StartDateTime )'; - 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 ) ) { + # EndTIme options + } elsif ( $term->{attr} eq 'EndDateTime' ) { + $self->{Sql} .= 'E.EndDateTime'; + } elsif ( $term->{attr} eq 'EndDate' ) { + $self->{Sql} .= 'to_days( E.EndDateTime )'; + } elsif ( $term->{attr} eq 'EndTime' ) { + $self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; + } elsif ( $term->{attr} eq 'EndWeekday' ) { + $self->{Sql} .= 'weekday( E.EndDateTime )'; + } elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { + push @{$self->{PostSQLConditions}}, $term; + $self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; + } elsif ( $term->{attr} eq 'DiskPercent' ) { + $self->{Sql} .= 'zmDiskPercent'; + $self->{HasDiskPercent} = !undef; + } elsif ( $term->{attr} eq 'DiskBlocks' ) { + $self->{Sql} .= 'zmDiskBlocks'; + $self->{HasDiskBlocks} = !undef; + } elsif ( $term->{attr} eq 'SystemLoad' ) { + $self->{Sql} .= 'zmSystemLoad'; + $self->{HasSystemLoad} = !undef; + } else { + $self->{Sql} .= 'E.'.$term->{attr}; + } - 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 { - $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' - ) { - $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' )"; - } - } 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'"; + } 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); } - push @value_list, $value; - } # end foreach temp_value - } # end if has an attr + } 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' + ) { + $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' )"; + } + } else { + $value = $temp_value; + } + push @value_list, $value; + } # end foreach temp_value if ( $term->{op} ) { if ( $term->{op} eq '=~' ) { @@ -314,7 +321,7 @@ sub Sql { } 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).')'; + $self->{Sql} .= ' IN ('.join(',', @value_list).")"; } elsif ( $term->{op} eq '![]' ) { $self->{Sql} .= ' NOT IN ('.join(',', @value_list).')'; } elsif ( $term->{op} eq 'LIKE' ) { @@ -326,9 +333,8 @@ sub Sql { } } # end if has an operator } # end if Pre/Post or SQL - if ( exists($term->{cbr}) ) { - $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}).' '; - } + $self->{Sql} .= ' '.str_repeat(')', $term->{cbr}) if exists($term->{cbr}); + $self->{Sql} .= "\n"; } # end foreach term } # end if terms diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index d68967fa9..b14b08aae 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -31,6 +31,8 @@ our %EXPORT_TAGS = ( systemStatus packageControl daemonControl + parseNameEqualsValueToHash + hash_diff ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -534,6 +536,42 @@ sub jsonDecode { return $result; } +sub parseNameEqualsValueToHash { + my %settings; + foreach my $line ( split ( /\r?\n/, $_[0] ) ) { + next if ! $line; + next if ! ( $line =~ /=/ ); + my ($name, $value ) = split('=', $line); + $value =~ s/^'//; + $value =~ s/'$//; + $settings{$name} = defined $value ? $value : ''; + } + return %settings; +} + +sub hash_diff { + # assumes keys of second hash are all in the first hash + my ( $settings, $defaults ) = @_; + my %updates; + + foreach my $setting ( keys %{$settings} ) { + next if ! exists $$defaults{$setting}; + if ( + ($$settings{$setting} and ! $$defaults{$setting}) + or + (!$$settings{$setting} and $$defaults{$setting}) + or + ( + ($$settings{$setting} and $$defaults{$setting} and ( + $$settings{$setting} ne $$defaults{$setting})) + ) + ) { + $updates{$setting} = $$defaults{$setting}; + } + } # end foreach setting + return %updates; +} + sub packageControl { my $command = shift; my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command; @@ -598,6 +636,8 @@ of the ZoneMinder scripts packageControl daemonControl systemStatus + parseNameEqualsValueToHash + hash_diff ) ] diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index cf5a0b508..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', @@ -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 { @@ -689,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' ) ) { @@ -735,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 4b6c6531c..e7fdcd35f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -163,13 +163,16 @@ our $mem_data = { format => { type=>'uint8', seq=>$mem_seq++ }, imagesize => { 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'=> { @@ -303,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; 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 bbcb5af71..c5e09c137 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -27,12 +27,17 @@ 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::Event_Summary; +require ZoneMinder::Zone; +use ZoneMinder::Logger qw(:all); #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); @@ -115,6 +120,7 @@ $serial = $primary_key = 'Id'; TrackDelay ReturnLocation ReturnDelay + ModectDuringPTZ DefaultRate DefaultScale SignalCheckPoints @@ -125,7 +131,6 @@ $serial = $primary_key = 'Id'; ZoneCount Refresh DefaultCodec - GroupIds Latitude Longitude ); @@ -133,8 +138,8 @@ $serial = $primary_key = 'Id'; %defaults = ( ServerId => 0, StorageId => 0, - Type => 'Ffmpeg', - Function => 'Mocord', + Type => q`'Ffmpeg'`, + Function => q`'Mocord'`, Enabled => 1, LinkedMonitors => undef, Device => '', @@ -163,15 +168,15 @@ $serial = $primary_key = 'Id'; VideoWriter => 0, OutputCodec => undef, OutputContainer => undef, - EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", + EncoderParameters => '', RecordAudio=>0, RTSPDescribe=>0, Brightness => -1, Contrast => -1, Hue => -1, Colour => -1, - EventPrefix => 'Event-', - LabelFormat => '%N - %d/%m/%y %H:%M:%S', + EventPrefix => q`'Event-'`, + LabelFormat => '', LabelX => 0, LabelY => 0, LabelSize => 1, @@ -201,16 +206,17 @@ $serial = $primary_key = 'Id'; TrackDelay => undef, ReturnLocation => -1, ReturnDelay => undef, + ModectDuringPTZ => 0, DefaultRate => 100, DefaultScale => 100, SignalCheckPoints => 0, - SignalCheckColour => '#0000BE', - WebColour => '#ff0000', + SignalCheckColour => q`'#0000BE'`, + WebColour => q`'#ff0000'`, Exif => 0, Sequence => undef, ZoneCount => 0, Refresh => undef, - DefaultCodec => 'auto', + DefaultCodec => q`'auto'`, Latitude => undef, Longitude => undef, ); @@ -223,6 +229,13 @@ 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; @@ -255,6 +268,81 @@ sub Status { return $$self{Status}; } +sub Event_Summary { + my $self = shift; + $$self{Event_Summary} = shift if @_; + if ( ! $$self{Event_Summary} ) { + $$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id}); + } + return $$self{Event_Summary}; +} + +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); + return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None'; + my $count = 50; + while ($count and ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { + ZoneMinder::Logger::Debug(1, 'Suspending motion detection'); + ZoneMinder::Memory::zmMonitorSuspend($self); + usleep(100000); + $count -= 1; + } + if (!$count) { + ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.'); + ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end + } else { + ZoneMinder::Logger::Debug(1, 'shared_data:active='.ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)); + } +} + +sub resumeMotionDetection { + my $self = shift; + return 0 if ! ZoneMinder::Memory::zmMemVerify($self); + return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None'; + my $count = 50; + while ($count and !ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { + ZoneMinder::Logger::Debug(1, 'Resuming motion detection'); + ZoneMinder::Memory::zmMonitorResume($self); + usleep(100000); + $count -= 1; + } + if (!$count) { + ZoneMinder::Logger::Error('Unable to resume motion detection after 5 seconds.'); + ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end + } + return 1; +} + +sub Control { + my $self = shift; + if ( ! exists $$self{Control}) { + require ZoneMinder::Control; + my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); + if ($Control) { + require Module::Load::Conditional; + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { + Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); + return undef; + } + bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + $$Control{MonitorId} = $$self{Id}; + $$self{Control} = $Control; + } + } + return $$self{Control}; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm index 9a9077653..1fafd3b0b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm @@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId'; CaptureFPS AnalysisFPS CaptureBandwidth - TotalEvents - TotalEventDiskSpace - HourEvents - HourEventDiskSpace - DayEvents - DayEventDiskSpace - WeekEvents - WeekEventDiskSpace - MonthEvents - MonthEventDiskSpace - ArchivedEvents - ArchivedEventDiskSpace ); %defaults = ( @@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId'; 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 { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index 0f8bcb8ad..e54bb15ce 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; @@ -212,7 +218,7 @@ sub save { my $serial = eval '$'.$type.'::serial'; my @identified_by = eval '@'.$type.'::identified_by'; - my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); + my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit}; if ( ! $serial ) { my $insert = $force_insert; my %serial = eval '%'.$type.'::serial'; @@ -228,8 +234,8 @@ $log->debug("No serial") if $debug; if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) { $where =~ s/\?/\%s/g; $log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $local_dbh->errstr; } elsif ( $debug ) { $log->debug("SQL succesful DELETE FROM $table WHERE $where"); @@ -261,8 +267,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -276,8 +282,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -315,8 +321,8 @@ $log->debug("No serial") if $debug; $command =~ s/\?/\%s/g; my $error = $local_dbh->errstr; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -334,8 +340,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log; - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -344,7 +350,7 @@ $log->debug("No serial") if $debug; } # end if } # end if } # end if - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; #$self->load(); #if ( $$fields{id} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { @@ -846,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 index 986c5a4dc..97c6d32e8 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Zone.pm @@ -40,6 +40,10 @@ $serial = $primary_key = 'Id'; MonitorId Type Units + NumCoords + Coords + Area + AlarmRGB CheckMethod MinPixelThreshold MaxPixelThreshold @@ -59,9 +63,13 @@ $serial = $primary_key = 'Id'; %defaults = ( Name => '', - Type => 'Active', - Units => 'Pixels', - CheckMethod => 'Blobs', + Type => q`'Active'`, + Units => q`'Pixels'`, + NumCoords => 0, + Coords => '', + Area => 0, + AlarmRGB => 0, + CheckMethod => q`'Blobs'`, MinPixelThreshold => undef, MaxPixelThreshold => undef, MinAlarmPixels => undef, diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 29ff7cab9..180aee58f 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -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); @@ -885,11 +885,11 @@ FROM `Frames` WHERE `EventId`=?'; $loop = $continuous; my $eventcounts_sql = ' - UPDATE `Monitor_Status` SET - `TotalEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitor_Status`.`MonitorId`), - `TotalEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitor_Status`.`MonitorId` AND `DiskSpace` IS NOT NULL), - `ArchivedEvents`=(SELECT COUNT(`Id`) FROM `Events` WHERE `MonitorId`=`Monitor_Status`.`MonitorId` AND `Archived`=1), - `ArchivedEventDiskSpace`=(SELECT SUM(`DiskSpace`) FROM `Events` WHERE `MonitorId`=`Monitor_Status`.`MonitorId` 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 `Monitor_Status` 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`=`Monitor_Status`.`MonitorId` SET - `Monitor_Status`.`HourEvents` = `E`.`HourEvents`, - `Monitor_Status`.`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 `Monitor_Status` 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`=`Monitor_Status`.`MonitorId` SET - `Monitor_Status`.`DayEvents` = `E`.`DayEvents`, - `Monitor_Status`.`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 `Monitor_Status` 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`=`Monitor_Status`.`MonitorId` SET - `Monitor_Status`.`WeekEvents` = `E`.`WeekEvents`, - `Monitor_Status`.`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 `Monitor_Status` 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`=`Monitor_Status`.`MonitorId` SET - `Monitor_Status`.`MonthEvents` = `E`.`MonthEvents`, - `Monitor_Status`.`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 474148df3..a9f26ac80 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage); use POSIX qw/strftime EPIPE EINTR/; use Socket; use Data::Dumper; -use Module::Load::Conditional qw{can_load}; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; @@ -61,12 +60,12 @@ GetOptions( 'autostop' =>\$options{autostop}, ) or pod2usage(-exitstatus => -1); -if ( !$id ) { +if (!$id) { print(STDERR "Please give a valid monitor id\n"); pod2usage(-exitstatus => -1); } -( $id ) = $id =~ /^(\w+)$/; +($id) = $id =~ /^(\w+)$/; logInit($id?(id=>'zmcontrol_'.$id):()); my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; @@ -76,7 +75,7 @@ socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $saddr = sockaddr_un($sock_file); -if ( $options{command} ) { +if ($options{command}) { # Have a command, so we are the client, connect to the server and send it. my $tries = 10; @@ -101,42 +100,29 @@ if ( $options{command} ) { Error("Unable to connect to zmcontrol server at $sock_file"); } } else { - # The server isn't there - my $monitor = zmDbGetMonitorAndControl($id); - if ( !$monitor ) { - Fatal("Unable to load control data for monitor $id"); - } - my $protocol = $monitor->{Protocol}; - if ( !$protocol ) { + require ZoneMinder::Monitor; + + my $monitor = ZoneMinder::Monitor->find_one(Id=>$id); + Fatal("Unable to load control data for monitor $id") if !$monitor; + + my $control = $monitor->Control(); + + my $protocol = $control->{Protocol}; + if (!$protocol) { Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); } - if ( -x $protocol ) { - # Protocol is actually a script! - # Holdover from previous versions - my $command .= $protocol.' '.$arg_string; - Debug($command); - - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) { - chomp($output); - Debug("Output: $output"); - } - if ( $status ) { - Error("Command '$command' exited with status: $status"); - exit($status); - } - exit(0); - } - Info("Starting control server $id/$protocol"); close(CLIENT); - if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { - 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()) @@ -144,14 +130,13 @@ if ( $options{command} ) { $0 = $0.' --id '.$id; - my $control = ('ZoneMinder::Control::'.$protocol)->new($id); my $control_key = $control->getKey(); $control->loadMonitor(); $control->open(); # If we have a command when starting up, then do it. - if ( $options{command} ) { + if ($options{command}) { my $command = $options{command}; $control->$command(\%options); } @@ -166,7 +151,7 @@ if ( $options{command} ) { 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) ) { @@ -202,7 +187,7 @@ if ( $options{command} ) { } else { Debug('Select timed out'); } - } # end while forever + } # end while !$zm_terminate Info("Control server $id/$protocol exiting"); unlink($sock_file); $control->close(); diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index c5eec566d..5cf866e56 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -101,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; diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index aaabedc8b..61922771b 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -399,7 +399,6 @@ sub checkFilter { ) { $Event->save(); } - $ZoneMinder::Database::dbh->commit() if !$$filter{LockRows}; } # end if UpdateDiskSpace } # end foreach event ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; @@ -527,7 +526,7 @@ sub uploadArchFile { return(0); } - my $archFile = $Event->{MonitorName}.'-'.$Event->{Id}; + my $archFile = $Event->Monitor()->Name().'-'.$Event->{Id}; my $archImagePath = $Event->Path() .'/' .( @@ -548,6 +547,10 @@ sub uploadArchFile { my $status = &AZ_OK; foreach my $imageFile ( @archImageFiles ) { + if (! -e $imageFile) { + Debug("Not adding $imageFile because it doesn't exist"); + next; + } Debug("Adding $imageFile"); my $member = $zip->addFile($imageFile); if ( !$member ) { @@ -662,13 +665,13 @@ sub substituteTags { # We have a filter and an event, do we need any more # monitor information? my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; - my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; - my $Status = $Monitor->Status() if $need_status; + my $Summary = $Monitor->Event_Summary() if $need_summary; # 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; @@ -689,19 +692,19 @@ sub substituteTags { } $rows ++; } - Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score"); + Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score"); $sth->finish(); } my $url = $Config{ZM_URL}; $text =~ s/%ZP%/$url/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/%MET%/$Summary->{TotalEvents}/g; + $text =~ s/%MEH%/$Summary->{HourEvents}/g; + $text =~ s/%MED%/$Summary->{DayEvents}/g; + $text =~ s/%MEW%/$Summary->{WeekEvents}/g; + $text =~ s/%MEM%/$Summary->{MonthEvents}/g; + $text =~ s/%MEA%/$Summary->{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; @@ -726,6 +729,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); @@ -773,9 +781,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 }; @@ -784,6 +790,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 @@ -827,7 +842,7 @@ sub sendEmail { return 0; } - Info('Creating notification email'); + Debug('Creating notification email'); my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event); return 0 if !$subject; @@ -835,7 +850,7 @@ sub sendEmail { my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments); return 0 if !$body; - Info("Sending notification email '$subject'"); + Debug("Sending notification email '$subject'"); eval { if ( $Config{ZM_NEW_MAIL_MODULES} ) { @@ -848,7 +863,7 @@ sub sendEmail { ); ### Add the text message part $mail->attach ( - Type => 'TEXT', + Type => (($body=~/ $body ); ### Add the attachments @@ -870,9 +885,7 @@ sub sendEmail { if ( $Config{ZM_SSMTP_MAIL} ) { my $ssmtp_location = $Config{ZM_SSMTP_PATH}; if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); - } + Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); $ssmtp_location = qx('which ssmtp'); } if ( !$ssmtp_location ) { @@ -900,7 +913,7 @@ sub sendEmail { foreach my $attachment ( @attachments ) { my $size = -s $attachment->{path}; $total_size += $size; - Info("Attaching '$attachment->{path}' which is $size bytes"); + Debug("Attaching '$attachment->{path}' which is $size bytes"); $mail->attach( Path => $attachment->{path}, @@ -918,7 +931,7 @@ sub sendEmail { Error("Unable to send email: $@"); return 0; } else { - Info('Notification email sent'); + Info("Notification email sent to $$filter{EmailTo}"); } my $sql = 'UPDATE `Events` SET `Emailed` = 1 WHERE `Id` = ?'; my $sth = $dbh->prepare_cached($sql) diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index b1647a26f..3302b0019 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -103,18 +103,17 @@ if ( $command eq 'state' ) { or Fatal("Can't prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: ()) or Fatal("Can't execute: ".$sth->errstr()); - while( my $monitor = $sth->fetchrow_hashref() ) { + while ( my $monitor = $sth->fetchrow_hashref() ) { foreach my $definition ( @{$state->{Definitions}} ) { if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) { $monitor->{NewFunction} = $definition->{Function}; $monitor->{NewEnabled} = $definition->{Enabled}; + last; } } - #next if ( !$monitor->{NewFunction} ); - $monitor->{NewFunction} = 'None' - if ( !$monitor->{NewFunction} ); - $monitor->{NewEnabled} = 0 - if ( !$monitor->{NewEnabled} ); + next if ! exists $monitor->{NewFunction}; + $monitor->{NewFunction} = 'None' if !$monitor->{NewFunction}; + $monitor->{NewEnabled} = 0 if !$monitor->{NewEnabled}; if ( $monitor->{Function} ne $monitor->{NewFunction} || $monitor->{Enabled} ne $monitor->{NewEnabled} ) { @@ -171,8 +170,8 @@ if ( $command =~ /^(?:start|restart)$/ ) { if ( $Config{ZM_DYN_DB_VERSION} and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION ) ) { - my ( $db_major, $db_minor, $db_micro ) = split('.', $Config{ZM_DYN_DB_VERSION}); - my ( $major, $minor, $micro ) = split('.', ZM_VERSION); + 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} @@ -295,6 +294,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/zmstats.pl.in b/scripts/zmstats.pl.in index 81fdd3129..a03f68ada 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -1,5 +1,6 @@ #!@PERL_EXECUTABLE@ -wT use strict; +use warnings; use bytes; # ========================================================================== @@ -28,13 +29,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()) ) { @@ -60,14 +68,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,26 +78,23 @@ 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 ); + $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 $rows; + } while ($rows and ($rows == 100) and !$zm_terminate); } } # 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)); + my $rows; + do { + # Delete any sessions that are more than a week old. Limiting to 100 because mysql sucks + $rows = zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - (60*60*24*7)); + Debug("Deleted $rows sessions") if $rows; + } while ($rows and ($rows == 100) and !$zm_terminate); 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 5fc632165..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 ) { @@ -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; @@ -312,7 +319,7 @@ while (1) { # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); -} # end while ( 1 ) +} # end while (!$zm_terminate) Info('Trigger daemon exiting'); exit; diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index b89f6a8f8..0253fadc2 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database =head1 SYNOPSIS -zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] +zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p ] =head1 DESCRIPTION @@ -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/zmwatch.pl.in b/scripts/zmwatch.pl.in index d82c74b99..96235ccd3 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}); @@ -91,19 +98,19 @@ while( 1 ) { next if $monitor->{Type} eq 'WebSite'; my $now = time(); my $restart = 0; - if ( zmMemVerify($monitor) ) { + if (zmMemVerify($monitor)) { # Check we have got an image recently my $capture_time = zmGetLastWriteTime($monitor); - if ( !defined($capture_time) ) { + if (!defined($capture_time)) { # Can't read from shared data Debug('LastWriteTime is not defined.'); zmMemInvalidate($monitor); next; } Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); - if ( !$capture_time ) { + if (!$capture_time) { my $startup_time = zmGetStartupTime($monitor); - if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) { + if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) { Warning( "Restarting capture daemon for $$monitor{Name}, no image since startup. ". "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" @@ -115,7 +122,7 @@ while( 1 ) { next; } } - if ( ! $restart ) { + if (!$restart) { my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) @@ -137,29 +144,28 @@ while( 1 ) { $restart = 1; } - if ( $restart ) { + if ($restart) { my $command; - if ( $monitor->{Type} eq 'Local' ) { - $command = "zmdc.pl restart zmc -d $monitor->{Device}"; + if ($monitor->{Type} eq 'Local') { + $command = 'zmdc.pl restart zmc -d '.$monitor->{Device}; } else { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + $command = 'zmdc.pl restart zmc -m '.$monitor->{Id}; } runCommand($command); - } elsif ( $monitor->{Function} ne 'Monitor' ) { + } elsif ($monitor->{Function} ne 'Monitor') { # Now check analysis daemon $restart = 0; # Check we have got an image recently my $image_time = zmGetLastReadTime($monitor); - if ( !defined($image_time) ) { + if (!defined($image_time)) { # Can't read from shared data $restart = 1; Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); - } elsif ( !$image_time ) { + } elsif (!$image_time) { # We can't get the last capture time so can't be sure it's died. #$restart = 1; Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); } else { - my $max_image_delay = ( $monitor->{MaxFPS} &&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}<1) @@ -168,7 +174,7 @@ while( 1 ) { ; my $image_delay = $now-$image_time; Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay"); - if ( $image_delay > $max_image_delay ) { + if ($image_delay > $max_image_delay) { Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," ." time since last analysis $image_delay seconds ($now-$image_time)" ); @@ -176,13 +182,13 @@ while( 1 ) { } } - if ( $restart ) { - Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); + if ($restart) { + Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}"); my $command; if ( $monitor->{Type} eq 'Local' ) { - $command = "zmdc.pl restart zmc -d $monitor->{Device}"; + $command = 'zmdc.pl restart zmc -d '.$monitor->{Device}; } else { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + $command = 'zmdc.pl restart zmc -m '.$monitor->{Id}; } runCommand($command); } # end if restart @@ -192,9 +198,9 @@ while( 1 ) { } # end foreach monitor sleep($Config{ZM_WATCH_CHECK_INTERVAL}); -} # end while (1) +} # end while (!$zm_terminate) -Info("Watchdog exiting"); +Info('Watchdog exiting'); exit(); 1; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4167dd8e..d4bc54e24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,26 +1,27 @@ # 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) +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 @@ -50,22 +51,17 @@ set(ZM_BIN_SRC_FILES zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp - zm_rtsp_server_thread.cpp - zm_rtsp_server_adts_source.cpp - zm_rtsp_server_h264_device_source.cpp - zm_rtsp_server_device_source.cpp - zm_rtsp_server_server_media_subsession.cpp - zm_rtsp_server_unicast_server_media_subsession.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_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) @@ -75,18 +71,43 @@ add_library(zm STATIC ${ZM_BIN_SRC_FILES}) target_include_directories(zm PUBLIC + ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(zm PUBLIC + FFMPEG::avcodec + FFMPEG::avformat + FFMPEG::avutil + FFMPEG::swresample + FFMPEG::swscale libbcrypt::bcrypt - jwt-cpp::jwt-cpp + RtspServer::RtspServer + martinmoene::span-lite + ${ZM_BIN_LIBS} PRIVATE zm-core-interface) +if(${ZM_JWT_BACKEND} STREQUAL "jwt_cpp") + target_link_libraries(zm + PUBLIC + jwt-cpp::jwt-cpp) +elseif(${ZM_JWT_BACKEND} STREQUAL "libjwt") + target_link_libraries(zm + PUBLIC + JWT::libjwt) +endif() + +if(TARGET V4L2::videodev2) + target_link_libraries(zm + PRIVATE + V4L2::videodev2) +endif() + add_executable(zmc zmc.cpp) -add_executable(zmu zmu.cpp) add_executable(zms zms.cpp) +add_executable(zmu zmu.cpp) +add_executable(zmbenchmark zmbenchmark.cpp) target_link_libraries(zmc PRIVATE @@ -94,7 +115,13 @@ target_link_libraries(zmc zm fmt::fmt ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} + ${CMAKE_DL_LIBS}) + +target_link_libraries(zms + PRIVATE + zm-core-interface + zm + ${ZM_EXTRA_LIBS} ${CMAKE_DL_LIBS}) target_link_libraries(zmu @@ -102,16 +129,14 @@ target_link_libraries(zmu zm-core-interface zm ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) -target_link_libraries(zms +target_link_libraries(zmbenchmark PRIVATE zm-core-interface zm fmt::fmt ${ZM_EXTRA_LIBS} - ${ZM_BIN_LIBS} ${CMAKE_DL_LIBS}) # Generate man files for the binaries destined for the bin folder @@ -126,3 +151,13 @@ install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE O 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} + ${CMAKE_DL_LIBS}) + 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/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 3e66940a0..325d8de59 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -1,55 +1,28 @@ #include "zm_analysis_thread.h" +#include "zm_monitor.h" #include "zm_signal.h" -#include "zm_utils.h" +#include "zm_time.h" -AnalysisThread::AnalysisThread(std::shared_ptr monitor) : - monitor_(std::move(monitor)), terminate_(false) { +AnalysisThread::AnalysisThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { thread_ = std::thread(&AnalysisThread::Run, this); } AnalysisThread::~AnalysisThread() { Stop(); - if (thread_.joinable()) - thread_.join(); + 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() { - Debug(2, "AnalysisThread::Run() for %d", monitor_->Id()); - - Microseconds analysis_rate = Microseconds(monitor_->GetAnalysisRate()); - Seconds analysis_update_delay = Seconds(monitor_->GetAnalysisUpdateDelay()); - Debug(2, "AnalysisThread::Run() have update delay %d", analysis_update_delay); - - 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"); - } + monitor_->Analyse(); } } diff --git a/src/zm_analysis_thread.h b/src/zm_analysis_thread.h index 8cf389e93..f949aa729 100644 --- a/src/zm_analysis_thread.h +++ b/src/zm_analysis_thread.h @@ -1,24 +1,27 @@ #ifndef ZM_ANALYSIS_THREAD_H #define ZM_ANALYSIS_THREAD_H -#include "zm_monitor.h" #include #include #include +class Monitor; + class AnalysisThread { public: - explicit AnalysisThread(std::shared_ptr monitor); + 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(); - std::shared_ptr monitor_; + Monitor *monitor_; std::atomic terminate_; std::thread thread_; }; diff --git a/src/zm_box.h b/src/zm_box.h index d7b5fc853..22210f2f8 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -20,49 +20,59 @@ #ifndef ZM_BOX_H #define ZM_BOX_H -#include "zm_coord.h" +#include "zm_line.h" +#include "zm_vector2.h" #include +#include // // Class used for storing a box, which is defined as a region // defined by two coordinates // class Box { -private: - Coord lo, hi; - Coord size; + public: + Box() = default; + Box(Vector2 lo, Vector2 hi) : lo_(lo), hi_(hi), size_(hi - lo) {} -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( 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 ) ) { } + const Vector2 &Lo() const { return lo_; } + const Vector2 &Hi() const { return hi_; } - 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(); } + const Vector2 &Size() const { return size_; } + int32 Area() const { return size_.x_ * size_.y_; } - inline const Coord Centre() const { - 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 ); + Vector2 Centre() const { + int32 mid_x = static_cast(std::lround(lo_.x_ + (size_.x_ / 2.0))); + int32 mid_y = static_cast(std::lround(lo_.y_ + (size_.y_ / 2.0))); + return {mid_x, mid_y}; } - inline bool Inside( const Coord &coord ) const - { - return( coord.X() >= lo.X() && coord.X() <= hi.X() && coord.Y() >= lo.Y() && coord.Y() <= hi.Y() ); + + // Get vertices of the box in a counter-clockwise order + std::vector Vertices() const { + return {lo_, {hi_.x_, lo_.y_}, hi_, {lo_.x_, hi_.y_}}; } + + // Get edges of the box in a counter-clockwise order + std::vector Edges() const { + std::vector edges; + edges.reserve(4); + + std::vector v = Vertices(); + edges.emplace_back(v[0], v[1]); + edges.emplace_back(v[1], v[2]); + edges.emplace_back(v[2], v[3]); + edges.emplace_back(v[3], v[0]); + + return edges; + } + + bool Contains(const Vector2 &coord) const { + return (coord.x_ >= lo_.x_ && coord.x_ <= hi_.x_ && coord.y_ >= lo_.y_ && coord.y_ <= hi_.y_); + } + + private: + Vector2 lo_; + Vector2 hi_; + Vector2 size_; }; #endif // ZM_BOX_H diff --git a/src/zm_buffer.cpp b/src/zm_buffer.cpp index e5d0d21c9..4cb580aa4 100644 --- a/src/zm_buffer.cpp +++ b/src/zm_buffer.cpp @@ -19,6 +19,8 @@ #include "zm_buffer.h" +#include + unsigned int Buffer::assign(const unsigned char *pStorage, unsigned int pSize) { if ( mAllocation < pSize ) { delete[] mStorage; @@ -37,8 +39,11 @@ unsigned int Buffer::expand(unsigned int count) { int headSpace = mHead - mStorage; int tailSpace = spare - headSpace; int width = mTail - mHead; - if ( spare > static_cast(count) ) { + 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; @@ -60,10 +65,29 @@ unsigned int Buffer::expand(unsigned int count) { 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, Microseconds timeout) { + fd_set set; + FD_ZERO(&set); /* clear the set */ + FD_SET(sd, &set); /* add our file descriptor to the set */ + timeval timeout_tv = zm::chrono::duration_cast(timeout); + + int rv = select(sd + 1, &set, nullptr, nullptr, &timeout_tv); + 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 1f9a0183a..fddd2f3cc 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -21,30 +21,39 @@ #define ZM_BUFFER_H #include "zm_logger.h" +#include "zm_time.h" #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) { 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]; 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]; std::memcpy(mStorage, buffer.mHead, mSize); mTail = mHead + mSize; @@ -62,7 +71,7 @@ public: } return mSize; } - //unsigned int Allocation() const { return( mAllocation ); } + // unsigned int Allocation() const { return( mAllocation ); } void clear() { mSize = 0; @@ -76,8 +85,9 @@ public: // 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); + if (count > mSize) { + Warning("Attempt to consume %d bytes of buffer, size is only %d bytes", + count, mSize); count = mSize; } mHead += count; @@ -87,29 +97,32 @@ public: } // 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); + 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; } // 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 + // 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); + 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; } // Add bytes to the end of the buffer @@ -126,52 +139,56 @@ public: 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 ) { - std::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 ) { + 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, Microseconds timeout); }; #endif // ZM_BUFFER_H diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index a4ebf485e..a73961ed9 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -54,6 +54,11 @@ Camera::Camera( mVideoStream(nullptr), mAudioStream(nullptr), mFormatContext(nullptr), + mSecondFormatContext(nullptr), + mFirstVideoPTS(0), + mFirstAudioPTS(0), + mLastVideoPTS(0), + mLastAudioPTS(0), bytes(0) { linesize = width * colours; @@ -68,12 +73,16 @@ Camera::~Camera() { if ( mFormatContext ) { // Should also free streams avformat_free_context(mFormatContext); - mVideoStream = nullptr; - mAudioStream = nullptr; } + if ( mSecondFormatContext ) { + // Should also free streams + avformat_free_context(mSecondFormatContext); + } + mVideoStream = nullptr; + mAudioStream = nullptr; } -AVStream *Camera::get_VideoStream() { +AVStream *Camera::getVideoStream() { if ( !mVideoStream ) { if ( !mFormatContext ) mFormatContext = avformat_alloc_context(); @@ -81,20 +90,12 @@ AVStream *Camera::get_VideoStream() { 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 + Debug(1, "Allocating avstream %p %p %d", mVideoStream, mVideoStream->codecpar, mVideoStream->codecpar->codec_id); } else { Error("Can't create video stream"); } diff --git a/src/zm_camera.h b/src/zm_camera.h index d3d7c4ce1..2caa8312d 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -24,6 +24,8 @@ #include #include +#include + class Monitor; class ZMPacket; @@ -56,7 +58,12 @@ protected: AVCodecContext *mAudioCodecContext; AVStream *mVideoStream; AVStream *mAudioStream; - AVFormatContext *mFormatContext; + 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: @@ -92,6 +99,8 @@ public: unsigned int Pixels() const { return pixels; } unsigned long long ImageSize() const { return imagesize; } unsigned int Bytes() const { return bytes; }; + int getFrequency() { return mAudioStream ? mAudioStream->codecpar->sample_rate : -1; } + int getChannels() { return mAudioStream ? mAudioStream->codecpar->channels : -1; } virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; } virtual int Hue( int/*p_hue*/=-1 ) { return -1; } @@ -105,16 +114,16 @@ public: //return (type == FFMPEG_SRC )||(type == REMOTE_SRC); } - virtual AVStream *get_VideoStream(); - virtual AVStream *get_AudioStream() { return mAudioStream; }; - virtual AVCodecContext *get_VideoCodecContext() { return mVideoCodecContext; }; - virtual AVCodecContext *get_AudioCodecContext() { return mAudioCodecContext; }; - int get_VideoStreamId() { return mVideoStreamId; }; - int get_AudioStreamId() { return mAudioStreamId; }; + 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(ZMPacket &p) = 0; + virtual int Capture(std::shared_ptr &p) = 0; virtual int PostCapture() = 0; virtual int Close() = 0; }; diff --git a/src/zm_comms.cpp b/src/zm_comms.cpp index 329dd3db2..2f57f9506 100644 --- a/src/zm_comms.cpp +++ b/src/zm_comms.cpp @@ -34,42 +34,44 @@ #include // define FIONREAD #endif -int ZM::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { +int zm::CommsBase::readV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; + std::vector iov(iovcnt); va_start(arg_ptr, iovcnt); - for ( int i = 0; i < iovcnt; i++ ) { + 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); - int nBytes = ::readv(mRd, iov, iovcnt); - if ( nBytes < 0 ) + int nBytes = ::readv(mRd, iov.data(), iovcnt); + if (nBytes < 0) { Debug(1, "Readv of %d buffers max on rd %d failed: %s", iovcnt, mRd, strerror(errno)); + } return nBytes; } -int ZM::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { +int zm::CommsBase::writeV(int iovcnt, /* const void *, int, */ ...) { va_list arg_ptr; - struct iovec iov[iovcnt]; + std::vector iov(iovcnt); va_start(arg_ptr, iovcnt); - for ( int i = 0; i < iovcnt; i++ ) { + 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); - ssize_t nBytes = ::writev(mWd, iov, iovcnt); - if ( nBytes < 0 ) + ssize_t nBytes = ::writev(mWd, iov.data(), iovcnt); + if (nBytes < 0) { Debug(1, "Writev of %d buffers on wd %d failed: %s", iovcnt, mWd, strerror(errno)); + } return nBytes; } -bool ZM::Pipe::open() { - if ( ::pipe(mFd) < 0 ) { +bool zm::Pipe::open() { + if (::pipe(mFd) < 0) { Error("pipe(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -77,28 +79,32 @@ bool ZM::Pipe::open() { return true; } -bool ZM::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; } -bool ZM::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 ) { + 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 { flags |= O_NONBLOCK; } - if ( fcntl(mFd[1], F_SETFL, flags) < 0 ) { + if (fcntl(mFd[1], F_SETFL, flags) < 0) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -106,77 +112,74 @@ bool ZM::Pipe::setBlocking(bool blocking) { return true; } -ZM::SockAddr::SockAddr(const struct sockaddr *addr) : mAddr(addr) { -} - -ZM::SockAddr *ZM::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); +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); } + Error("Unable to create new SockAddr from addr family %d with size %d", addr.sa_family, len); return nullptr; } -ZM::SockAddr *ZM::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); - } else if ( addr->getDomain() == AF_UNIX ) { - return new SockAddrUnix(*(SockAddrUnix *)addr); } + + if (addr->getDomain() == AF_INET) { + return new SockAddrInet(*(SockAddrInet *) addr); + } else if (addr->getDomain() == AF_UNIX) { + return new SockAddrUnix(*(SockAddrUnix *) addr); + } + Error("Unable to create new SockAddr from addr family %d", addr->getDomain()); return nullptr; } -ZM::SockAddrInet::SockAddrInet() : SockAddr( (struct sockaddr *)&mAddrIn ) { -} - -bool ZM::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { +bool zm::SockAddrInet::resolve(const char *host, const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct hostent *hostent = nullptr; - if ( !(hostent = ::gethostbyname(host) ) ) { + 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) ) ) { + 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; } -bool ZM::SockAddrInet::resolve(const char *host, int port, const char *proto) { +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)) ) { + 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; + mAddrIn.sin_addr.s_addr = ((in_addr *) (hostent->h_addr))->s_addr; return true; } -bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { +bool zm::SockAddrInet::resolve(const char *serv, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); - struct servent *servent = nullptr; - if ( !(servent = ::getservbyname(serv, proto)) ) { + servent *servent = nullptr; + if (!(servent = ::getservbyname(serv, proto))) { Error("getservbyname(%s), errno = %d, error = %s", serv, errno, strerror(errno)); return false; } @@ -188,7 +191,7 @@ bool ZM::SockAddrInet::resolve(const char *serv, const char *proto) { return true; } -bool ZM::SockAddrInet::resolve(int port, const char *proto) { +bool zm::SockAddrInet::resolve(int port, const char *proto) { memset(&mAddrIn, 0, sizeof(mAddrIn)); mAddrIn.sin_port = htons(port); @@ -198,57 +201,56 @@ bool ZM::SockAddrInet::resolve(int port, const char *proto) { return true; } -ZM::SockAddrUnix::SockAddrUnix() : SockAddr((struct sockaddr *)&mAddrUn ) { -} - -bool ZM::SockAddrUnix::resolve(const char *path, const char *proto) { +bool zm::SockAddrUnix::resolve(const char *path, const char *proto) { memset(&mAddrUn, 0, sizeof(mAddrUn)); strncpy(mAddrUn.sun_path, path, sizeof(mAddrUn.sun_path)); + mAddrUn.sun_path[sizeof(mAddrUn.sun_path) - 1] = '\0'; mAddrUn.sun_family = AF_UNIX; return true; } -bool ZM::Socket::socket() { - if ( mSd >= 0 ) +bool zm::Socket::socket() { + if (mSd >= 0) { return true; + } - if ( (mSd = ::socket(getDomain(), getType(), 0) ) < 0 ) { + 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; } -bool ZM::Socket::connect() { - if ( !socket() ) +bool zm::Socket::connect() { + if (!socket()) { return false; + } - if ( ::connect(mSd, mRemoteAddr->getAddr(), getAddrSize()) == -1 ) { + if (::connect(mSd, mRemoteAddr->getAddr(), getAddrSize()) == -1) { Error("connect(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; } mState = CONNECTED; - return true; } -bool ZM::Socket::bind() { - if ( !socket() ) +bool zm::Socket::bind() { + if (!socket()) { return false; + } - if ( ::bind(mSd, mLocalAddr->getAddr(), getAddrSize()) == -1 ) { + if (::bind(mSd, mLocalAddr->getAddr(), getAddrSize()) == -1) { Error("bind(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; @@ -256,8 +258,8 @@ bool ZM::Socket::bind() { return true; } -bool ZM::Socket::listen() { - if ( ::listen(mSd, SOMAXCONN) == -1 ) { +bool zm::Socket::listen() { + if (::listen(mSd, SOMAXCONN) == -1) { Error("listen(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; @@ -267,12 +269,12 @@ bool ZM::Socket::listen() { return true; } -bool ZM::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 ) { + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; @@ -282,16 +284,15 @@ bool ZM::Socket::accept() { mSd = newSd; mState = CONNECTED; - return true; } -bool ZM::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 ) { + if ((newSd = ::accept(mSd, &rem_addr, &rem_addr_size)) == -1) { Error("accept(), errno = %d, error = %s", errno, strerror(errno)); close(); return false; @@ -300,27 +301,30 @@ bool ZM::Socket::accept(int &newSd) { return true; } -bool ZM::Socket::close() { - if ( mSd > -1 ) ::close(mSd); +bool zm::Socket::close() { + if (mSd > -1) { + ::close(mSd); + } + mSd = -1; mState = CLOSED; return true; } -int ZM::Socket::bytesToRead() const { +int zm::Socket::bytesToRead() const { int bytes_to_read = 0; - if ( ioctl(mSd, FIONREAD, &bytes_to_read) < 0 ) { + if (ioctl(mSd, FIONREAD, &bytes_to_read) < 0) { Error("ioctl(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return bytes_to_read; } -bool ZM::Socket::getBlocking(bool &blocking) { +bool zm::Socket::getBlocking(bool &blocking) { int flags; - if ( (flags = fcntl(mSd, F_GETFL)) < 0 ) { + if ((flags = fcntl(mSd, F_GETFL)) < 0) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -328,20 +332,20 @@ bool ZM::Socket::getBlocking(bool &blocking) { return true; } -bool ZM::Socket::setBlocking(bool blocking) { +bool zm::Socket::setBlocking(bool blocking) { int flags; /* Now set it for non-blocking I/O */ - if ( (flags = fcntl(mSd, F_GETFL)) < 0 ) { + 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 { flags |= O_NONBLOCK; } - if ( fcntl(mSd, F_SETFL, flags) < 0 ) { + if (fcntl(mSd, F_SETFL, flags) < 0) { Error("fcntl(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -349,44 +353,44 @@ bool ZM::Socket::setBlocking(bool blocking) { return true; } -bool ZM::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 ) { + if (getsockopt(mSd, SOL_SOCKET, SO_SNDBUF, &buffersize, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return buffersize; } -bool ZM::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 ) { + if (getsockopt(mSd, SOL_SOCKET, SO_RCVBUF, &buffersize, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return -1; } return buffersize; } -bool ZM::Socket::setSendBufferSize(int buffersize) { - if ( setsockopt(mSd, SOL_SOCKET, SO_SNDBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) { +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; } -bool ZM::Socket::setRecvBufferSize(int buffersize) { - if ( setsockopt( mSd, SOL_SOCKET, SO_RCVBUF, (char *)&buffersize, sizeof(buffersize)) < 0 ) { +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; } -bool ZM::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 ) { + if (getsockopt(mSd, SOL_SOCKET, SO_DONTROUTE, &dontRoute, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -394,19 +398,19 @@ bool ZM::Socket::getRouting(bool &route) const { return true; } -bool ZM::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 ) { + 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; } -bool ZM::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 ) { + if (getsockopt(mSd, IPPROTO_TCP, TCP_NODELAY, &int_nodelay, &optlen) < 0) { Error("getsockopt(), errno = %d, error = %s", errno, strerror(errno)); return false; } @@ -414,31 +418,31 @@ bool ZM::Socket::getNoDelay(bool &nodelay) const { return true; } -bool ZM::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 ) { + 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; } -bool ZM::InetSocket::connect(const char *host, const char *serv) { - struct addrinfo hints; - struct addrinfo *result, *rp; +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(struct addrinfo)); + 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 ) { + if (s != 0) { Error("connect(): getaddrinfo: %s", gai_strerror(s)); return false; } @@ -448,61 +452,65 @@ bool ZM::InetSocket::connect(const char *host, const char *serv) { * 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 ) + 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); + 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, &((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, buf, sizeof(buf)-1); + inet_ntop(AF_INET6, &((sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, sizeof(buf) - 1); } else { - strncpy(buf, "n/a", sizeof(buf)-1); + 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 ) + 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)); + ::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 ) + if (::connect(mSd, rp->ai_addr, rp->ai_addrlen) != -1) { break; /* Success */ + } ::close(mSd); - } // end for + } freeaddrinfo(result); /* No longer needed */ - if ( rp == nullptr ) { /* No address succeeded */ + if (rp == nullptr) { /* No address succeeded */ Error("connect(), Could not connect"); mAddressFamily = AF_UNSPEC; return false; } mState = CONNECTED; - return true; } -bool ZM::InetSocket::connect(const char *host, int port) { +bool zm::InetSocket::connect(const char *host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return connect(host, serv); } -bool ZM::InetSocket::bind(const char * host, const char * serv) { - struct addrinfo hints; +bool zm::InetSocket::bind(const char *host, const char *serv) { + addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); + 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 */ @@ -511,9 +519,9 @@ bool ZM::InetSocket::bind(const char * host, const char * serv) { hints.ai_addr = nullptr; hints.ai_next = nullptr; - struct addrinfo *result, *rp; + addrinfo *result, *rp; int s = getaddrinfo(host, serv, &hints, &result); - if ( s != 0 ) { + if (s != 0) { Error("bind(): getaddrinfo: %s", gai_strerror(s)); return false; } @@ -523,29 +531,32 @@ bool ZM::InetSocket::bind(const char * host, const char * serv) { * 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 ) { + 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); + 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); + 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 ) + if (mSd == -1) { continue; + } mState = DISCONNECTED; - if ( ::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0 ) + if (::bind(mSd, rp->ai_addr, rp->ai_addrlen) == 0) { break; /* Success */ + } ::close(mSd); mSd = -1; - } // end foreach result + } - if ( rp == nullptr ) { /* No address succeeded */ + if (rp == nullptr) { /* No address succeeded */ Error("bind(), Could not bind"); return false; } @@ -554,193 +565,166 @@ bool ZM::InetSocket::bind(const char * host, const char * serv) { return true; } -bool ZM::InetSocket::bind(const char * serv) { +bool zm::InetSocket::bind(const char *serv) { return bind(nullptr, serv); } -bool ZM::InetSocket::bind(const char * host, int port) { +bool zm::InetSocket::bind(const char *host, int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(host, serv); } -bool ZM::InetSocket::bind(int port) { +bool zm::InetSocket::bind(int port) { char serv[8]; snprintf(serv, sizeof(serv), "%d", port); return bind(nullptr, serv); } -bool ZM::TcpInetServer::listen() { +bool zm::TcpInetServer::listen() { return Socket::listen(); } -bool ZM::TcpInetServer::accept() { +bool zm::TcpInetServer::accept() { return Socket::accept(); } -bool ZM::TcpInetServer::accept(TcpInetSocket *&newSocket) { +bool zm::TcpInetServer::accept(TcpInetSocket *&newSocket) { int newSd = -1; newSocket = nullptr; - if ( !Socket::accept(newSd) ) + if (!Socket::accept(newSd)) { return false; + } newSocket = new TcpInetSocket(*this, newSd); - return true; } -bool ZM::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { +bool zm::TcpUnixServer::accept(TcpUnixSocket *&newSocket) { int newSd = -1; newSocket = nullptr; - if ( !Socket::accept(newSd) ) + if (!Socket::accept(newSd)) { return false; + } newSocket = new TcpUnixSocket(*this, newSd); - return true; } -ZM::Select::Select() : mHasTimeout(false), mMaxFd(-1) { -} - -ZM::Select::Select(struct timeval timeout) : mMaxFd(-1) { - setTimeout(timeout); -} - -ZM::Select::Select(int timeout) : mMaxFd(-1) { - setTimeout(timeout); -} - -ZM::Select::Select(double timeout) : mMaxFd(-1) { - setTimeout(timeout); -} - -void ZM::Select::setTimeout(int timeout) { - mTimeout.tv_sec = timeout; - mTimeout.tv_usec = 0; - mHasTimeout = true; -} - -void ZM::Select::setTimeout(double timeout) { - mTimeout.tv_sec = int(timeout); - mTimeout.tv_usec = suseconds_t((timeout-mTimeout.tv_sec)*1000000.0); - mHasTimeout = true; -} - -void ZM::Select::setTimeout(struct timeval timeout) { +void zm::Select::setTimeout(Microseconds timeout) { mTimeout = timeout; mHasTimeout = true; } -void ZM::Select::clearTimeout() { +void zm::Select::clearTimeout() { mHasTimeout = false; } -void ZM::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 ZM::Select::addReader(CommsBase *comms) { - if ( !comms->isOpen() ) { +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 ) + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); + } } return result.second; } -bool ZM::Select::deleteReader(CommsBase *comms) { - if ( !comms->isOpen() ) { +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 false; } -void ZM::Select::clearReaders() { +void zm::Select::clearReaders() { mReaders.clear(); mMaxFd = -1; } -bool ZM::Select::addWriter(CommsBase *comms) { +bool zm::Select::addWriter(CommsBase *comms) { std::pair result = mWriters.insert(comms); - if ( result.second ) { - if ( comms->getMaxDesc() > mMaxFd ) + if (result.second) { + if (comms->getMaxDesc() > mMaxFd) { mMaxFd = comms->getMaxDesc(); + } } return result.second; } -bool ZM::Select::deleteWriter(CommsBase *comms) { - if ( mWriters.erase(comms) ) { +bool zm::Select::deleteWriter(CommsBase *comms) { + if (mWriters.erase(comms)) { calcMaxFd(); return true; } return false; } -void ZM::Select::clearWriters() { +void zm::Select::clearWriters() { mWriters.clear(); mMaxFd = -1; } -int ZM::Select::wait() { - struct timeval tempTimeout = mTimeout; - struct timeval *selectTimeout = mHasTimeout?&tempTimeout:nullptr; +int zm::Select::wait() { + timeval tempTimeout = zm::chrono::duration_cast(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 ) + 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 ) + 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 ) { + int nFound = select(mMaxFd + 1, &rfds, &wfds, nullptr, selectTimeout); + if (nFound == 0) { Debug(1, "Select timed out"); - } else if ( nFound < 0 ) { + } 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) ) + 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) ) + } + } + for (CommsSet::iterator iter = mWriters.begin(); iter != mWriters.end(); ++iter) { + if (FD_ISSET((*iter)->getWriteDesc(), &rfds)) { mWriteable.push_back(*iter); + } + } } return nFound; } - -const ZM::Select::CommsList &ZM::Select::getReadable() const { - return mReadable; -} - -const ZM::Select::CommsList &ZM::Select::getWriteable() const { - return mWriteable; -} diff --git a/src/zm_comms.h b/src/zm_comms.h index 30c217ddc..7e7329d5d 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -22,11 +22,13 @@ #include "zm_exception.h" #include "zm_logger.h" +#include "zm_time.h" #include #include #include #include #include +#include #include #if defined(BSD) @@ -34,616 +36,559 @@ #include #endif -namespace ZM { +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(const int &rd, const 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 { - 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 ); + virtual ssize_t recv(std::string &msg) const { + std::vector buffer(msg.capacity()); + ssize_t nBytes; + if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) { + Debug(1, "Recv of %zd bytes max to string on sd %d failed: %s", msg.size(), mSd, strerror(errno)); + return nBytes; } buffer[nBytes] = '\0'; - msg = buffer; - return( nBytes ); + msg = {buffer.begin(), buffer.begin() + nBytes}; + return nBytes; } - 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 ); + + virtual ssize_t recv(std::string &msg, size_t maxLen) const { + std::vector buffer(maxLen); + ssize_t nBytes; + if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 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 ); + msg = {buffer.begin(), buffer.begin() + 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(Microseconds 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(Microseconds 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; + Microseconds mTimeout; + int mMaxFd; }; } diff --git a/src/zm_config.cpp b/src/zm_config.cpp index f7cd609bf..e2f0e53f0 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -39,16 +39,15 @@ void zmLoadStaticConfig() { // update the Config hash with those values 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); + std::string glob_pattern = stringtf("%s/*.conf", ZM_CONFIG_SUBDIR); glob_t pglob; - int glob_status = glob(glob_pattern, 0, nullptr, &pglob); + int glob_status = glob(glob_pattern.c_str(), 0, nullptr, &pglob); if (glob_status != 0) { if (glob_status < 0) { - Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); + Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno)); } else { - Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); + Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status); } } else { for (unsigned int i = 0; i < pglob.gl_pathc; i++) { @@ -75,7 +74,7 @@ void zmLoadDBConfig() { std::string sql = stringtf("SELECT `Id` FROM `Servers` WHERE `Name`='%s'", staticConfig.SERVER_NAME.c_str()); zmDbRow dbrow; - if (dbrow.fetch(sql.c_str())) { + if (dbrow.fetch(sql)) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str()); @@ -87,7 +86,7 @@ void zmLoadDBConfig() { 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)) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID); @@ -100,13 +99,10 @@ void zmLoadDBConfig() { } } - 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"); + staticConfig.capture_file_format = stringtf("%%s/%%0%dd-capture.jpg", config.event_image_digits); + staticConfig.analyse_file_format = stringtf("%%s/%%0%dd-analyse.jpg", config.event_image_digits); + staticConfig.general_file_format = stringtf("%%s/%%0%dd-%%s", config.event_image_digits); + staticConfig.video_file_format = "%s/%s"; } void process_configfile(char const *configFile) { @@ -340,16 +336,11 @@ Config::~Config() { } void Config::Load() { - if ( mysql_query(&dbconn, "SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`") ) { - 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,6 @@ void Config::Load() { items[i] = new ConfigItem(dbrow[0], dbrow[1], dbrow[2]); } mysql_free_result(result); - result = nullptr; } void Config::Assign() { diff --git a/src/zm_config.h b/src/zm_config.h index daea8e14c..e2ec9781a 100644 --- a/src/zm_config.h +++ b/src/zm_config.h @@ -25,14 +25,6 @@ #include "zm_config_defines.h" #include -#if !defined(PATH_MAX) -#define PATH_MAX 1024 -#endif - -#ifdef HAVE_LIBAVFORMAT -#define ZM_HAS_FFMPEG 1 -#endif // HAVE_LIBAVFORMAT - #define ZM_MAX_IMAGE_WIDTH 2048 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_HEIGHT 1536 // The largest image we imagine ever handling #define ZM_MAX_IMAGE_COLOURS 4 // The largest image we imagine ever handling @@ -42,11 +34,6 @@ #define ZM_SCALE_BASE 100 // The factor by which we bump up 'scale' to simulate FP #define ZM_RATE_BASE 100 // The factor by which we bump up 'rate' to simulate FP -#define ZM_SQL_BATCH_SIZE 50 // Limit the size of multi-row SQL statements -#define ZM_SQL_SML_BUFSIZ 256 // Size of SQL buffer -#define ZM_SQL_MED_BUFSIZ 1024 // Size of SQL buffer -#define ZM_SQL_LGE_BUFSIZ 8192 // Size of SQL buffer - #define ZM_NETWORK_BUFSIZ 32768 // Size of network buffer #define ZM_MAX_FPS 30 // The maximum frame rate we expect to handle @@ -78,10 +65,10 @@ struct StaticConfig { std::string PATH_LOGS; std::string PATH_SWAP; std::string PATH_ARP; - char capture_file_format[PATH_MAX]; - char analyse_file_format[PATH_MAX]; - char general_file_format[PATH_MAX]; - char video_file_format[PATH_MAX]; + std::string capture_file_format; + std::string analyse_file_format; + std::string general_file_format; + std::string video_file_format; }; extern StaticConfig staticConfig; diff --git a/src/zm_config_data.h.in b/src/zm_config_data.h.in index 1209dcbf5..f16135e92 100644 --- a/src/zm_config_data.h.in +++ b/src/zm_config_data.h.in @@ -24,8 +24,4 @@ #define ZM_CONFIG_SUBDIR "@ZM_CONFIG_SUBDIR@" // Path to the ZoneMinder config subfolder #define ZM_VERSION "@VERSION@" // ZoneMinder Version -#define ZM_HAS_V4L1 @ZM_HAS_V4L1@ -#define ZM_HAS_V4L2 @ZM_HAS_V4L2@ -#define ZM_HAS_V4L @ZM_HAS_V4L@ - #endif // ZM_CONFIG_DATA_H diff --git a/src/zm_coord.cpp b/src/zm_coord.cpp deleted file mode 100644 index 0b7ab0e71..000000000 --- a/src/zm_coord.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// ZoneMinder Coordinate 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_coord.h" - -// This section deliberately left blank diff --git a/src/zm_coord.h b/src/zm_coord.h deleted file mode 100644 index b7e8fd046..000000000 --- a/src/zm_coord.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// ZoneMinder Coordinate 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_COORD_H -#define ZM_COORD_H - -#include "zm_define.h" - -// -// Class used for storing an x,y pair, i.e. a coordinate -// -class Coord { -private: - int x, y; - -public: - inline Coord() : x(0), y(0) { } - inline Coord( int p_x, int p_y ) : x(p_x), y(p_y) { } - inline Coord( const Coord &p_coord ) : x(p_coord.x), y(p_coord.y) { } - inline Coord &operator =( const Coord &coord ) { - x = coord.x; - y = coord.y; - return *this; - } - 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; - } - - 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 ); } - - inline friend Coord operator+( const Coord &coord1, const Coord &coord2 ) { Coord result( coord1 ); result += coord2; return( result ); } - inline friend Coord operator-( const Coord &coord1, const Coord &coord2 ) { Coord result( coord1 ); result -= coord2; return( result ); } -}; - -#endif // ZM_COORD_H diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 0682970c1..5f4139b61 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,6 +1,7 @@ #include "zm_crypt.h" #include "zm_logger.h" +#include "zm_utils.h" #include "BCrypt.hpp" #include #include @@ -8,14 +9,7 @@ #if HAVE_LIBJWT #include #else -#include "jwt_cpp.h" -#endif - -#if HAVE_LIBCRYPTO -#include -#elif HAVE_GNUTLS_GNUTLS_H -#include -#include +#include #endif // returns username if valid, "" if not @@ -27,38 +21,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); @@ -68,7 +63,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); @@ -93,26 +88,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 { @@ -120,19 +116,21 @@ 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); } #endif // HAVE_LIBJWT bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) { + using namespace zm::crypto; + bool password_correct = false; if ( strlen(db_password_hash) < 4 ) { // actually, shoud be more, but this is min. for next code @@ -141,46 +139,13 @@ bool verifyPassword(const char *username, const char *input_password, const char } if ( db_password_hash[0] == '*' ) { // MYSQL PASSWORD - Debug(1, "%s is using an MD5 encoded password", username); - - #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 - SHA_CTX ctx1, ctx2; - - //get first iteration - SHA1_Init(&ctx1); - SHA1_Update(&ctx1, input_password, strlen(input_password)); - SHA1_Final(digest_interim, &ctx1); + Debug(1, "%s is using an SHA1 encoded password", username); - //2nd iteration - SHA1_Init(&ctx2); - SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); - SHA1_Final (digest_final, &ctx2); -#elif HAVE_GNUTLS_GNUTLS_H - //get first iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, input_password, strlen(input_password), digest_interim); - //2nd iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, digest_interim, SHA_DIGEST_LENGTH, digest_final); -#else - Error("Authentication Error. ZoneMinder not built with GnuTLS or Openssl"); - return false; -#endif + SHA1::Digest digest = SHA1::GetDigestOf(SHA1::GetDigestOf(input_password)); + std::string hex_digest = '*' + StringToUpper(ByteArrayToHexString(digest)); - char final_hash[SHA_DIGEST_LENGTH * 2 +2]; - final_hash[0] = '*'; - //convert to hex - for ( int i = 0; i < SHA_DIGEST_LENGTH; i++ ) - sprintf(&final_hash[i*2]+1, "%02X", (unsigned int)digest_final[i]); - final_hash[SHA_DIGEST_LENGTH *2 + 1] = 0; - - Debug(1, "Computed password_hash:%s, stored password_hash:%s", final_hash, db_password_hash); - password_correct = (strcmp(db_password_hash, final_hash)==0); + Debug(1, "Computed password_hash: %s, stored password_hash: %s", hex_digest.c_str(), db_password_hash); + password_correct = (strcmp(db_password_hash, hex_digest.c_str()) == 0); } else if ( (db_password_hash[0] == '$') && @@ -191,7 +156,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 15dbc9332..b495dd536 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -20,10 +20,36 @@ #ifndef ZM_CRYPT_H #define ZM_CRYPT_H +#include "zm_config.h" +#include "zm_crypto_gnutls.h" +#include "zm_crypto_openssl.h" +#include "zm_define.h" #include -#include -bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash); +bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash); -std::pair verifyToken(std::string token, std::string key); -#endif // ZM_CRYPT_H \ No newline at end of file +std::pair verifyToken(std::string token, std::string key); + +namespace zm { +namespace crypto { +namespace impl { + +#if defined(HAVE_LIBGNUTLS) +template +using Hash = gnutls::GenericHashImpl; +#elif defined(HAVE_LIBOPENSSL) +template +using Hash = openssl::GenericHashImpl; +#endif +} +} +} + +namespace zm { +namespace crypto { +using MD5 = impl::Hash; +using SHA1 = impl::Hash; +} +} + +#endif // ZM_CRYPT_H diff --git a/src/zm_crypto_generics.h b/src/zm_crypto_generics.h new file mode 100644 index 000000000..9661d4b61 --- /dev/null +++ b/src/zm_crypto_generics.h @@ -0,0 +1,108 @@ +/* + * 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 ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ + +#include "zm_define.h" +#include "zm_utils.h" +#include +#include + +namespace zm { +namespace crypto { +namespace impl { + +enum class HashAlgorithms { + kMD5, + kSHA1 +}; + +template +struct HashAlgorithm; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 16; +}; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 20; +}; + +template +class GenericHash { + public: + static constexpr size_t DIGEST_LENGTH = HashAlgorithm::digest_length; + using Digest = std::array; + + static Digest GetDigestOf(uint8 const *data, size_t len) { + Impl hash; + hash.UpdateData(data, len); + hash.Finalize(); + return hash.GetDigest(); + } + + template + static Digest GetDigestOf(Ts &&... pack) { + Impl hash; + UpdateData(hash, std::forward(pack)...); + hash.Finalize(); + return hash.GetDigest(); + } + + void UpdateData(const uint8 *data, size_t length) { + static_cast(*this).DoUpdateData(data, length); + } + void UpdateData(const std::string &str) { + UpdateData(reinterpret_cast(str.c_str()), str.size()); + } + void UpdateData(const char *str) { + UpdateData(reinterpret_cast(str), strlen(str)); + } + template + void UpdateData(Container const &c) { + UpdateData(zm::data(c), zm::size(c)); + } + + void Finalize() { + static_cast(*this).DoFinalize(); + } + + const Digest &GetDigest() const { return digest_; } + + protected: + Digest digest_ = {}; + + private: + template + static void UpdateData(Impl &hash, T const &data) { + hash.UpdateData(data); + } + + template + static void UpdateData(Impl &hash, T const &data, TRest &&... rest) { + hash.UpdateData(data); + UpdateData(hash, std::forward(rest)...); + } +}; +} +} +} + +#endif //ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ diff --git a/src/zm_crypto_gnutls.h b/src/zm_crypto_gnutls.h new file mode 100644 index 000000000..d2c51f35d --- /dev/null +++ b/src/zm_crypto_gnutls.h @@ -0,0 +1,75 @@ +/* + * 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 ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ + +#ifdef HAVE_LIBGNUTLS + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace gnutls { + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5; +}; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_SHA1; +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + int32 ret = gnutls_hash_init(&handle_, HashAlgorithmMapper::algorithm); + ASSERT(ret == 0); + }; + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = gnutls_hash(handle_, data, length); + ASSERT(res == 0); + } + + void DoFinalize() { + gnutls_hash_deinit(handle_, digest_.data()); + } + + private: + gnutls_hash_hd_t handle_ = {}; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBGNUTLS + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ diff --git a/src/zm_crypto_openssl.h b/src/zm_crypto_openssl.h new file mode 100644 index 000000000..667142531 --- /dev/null +++ b/src/zm_crypto_openssl.h @@ -0,0 +1,108 @@ +/* + * 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 ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ + +#ifdef HAVE_LIBOPENSSL + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace openssl { + +typedef EVP_MD const *(*HashCreator)(); + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_md5; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_md5; +#endif +}; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_sha1; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_sha1; +#endif +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + // TODO: Use EVP_MD_CTX_new once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + ctx_ = EVP_MD_CTX_create(); +#if defined(__GNUC__) && __GNUC__ < 5 + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator()(), nullptr); +#else + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator(), nullptr); +#endif + }; + + ~GenericHashImpl() { + // TODO: Use EVP_MD_CTX_free once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + EVP_MD_CTX_destroy(ctx_); + } + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = EVP_DigestUpdate(ctx_, data, length); + ASSERT(res == 1); + } + + void DoFinalize() { + uint32 length = 0; + int32 res = EVP_DigestFinal_ex(ctx_, digest_.data(), &length); + ASSERT(res == 1); + ASSERT(length == HashAlgorithm::digest_length); + } + + private: + EVP_MD_CTX *ctx_; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBOPENSSL + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 9baf21572..27a213ed1 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -1,17 +1,17 @@ // // ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $ // 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. @@ -46,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 @@ -69,19 +73,35 @@ void bind_libcurl_symbols() { *(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup"); } -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 ) +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) { - - if ( capture ) { + if (capture) { Initialise(); } } cURLCamera::~cURLCamera() { - if ( capture ) { - + if (capture) { Terminate(); } } @@ -91,40 +111,40 @@ void cURLCamera::Initialise() { content_type_match_len = strlen(content_type_match); databuffer.expand(CURL_BUFFER_INITIAL_SIZE); - + bind_libcurl_symbols(); /* cURL initialization */ CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL); - if(cRet != CURLE_OK) { - Error("libcurl initialization failed: ", (*curl_easy_strerror_f)(cRet)); + if (cRet != CURLE_OK) { + Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet)); dlclose(curl_lib); return; } - Debug(2,"libcurl version: %s", (*curl_version_f)()); + Debug(2, "libcurl version: %s", (*curl_version_f)()); /* Create the shared data mutex */ int nRet = pthread_mutex_init(&shareddata_mutex, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Shared data mutex creation failed: %s",strerror(nRet)); return; } /* Create the data available condition variable */ nRet = pthread_cond_init(&data_available_cond, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Data available condition variable creation failed: %s",strerror(nRet)); return; } /* Create the request complete condition variable */ nRet = pthread_cond_init(&request_complete_cond, nullptr); - if(nRet != 0) { + if (nRet != 0) { Error("Request complete condition variable creation failed: %s",strerror(nRet)); return; } /* Create the thread */ nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this); - if(nRet != 0) { + if (nRet != 0) { Error("Thread creation failed: %s",strerror(nRet)); return; } @@ -147,21 +167,22 @@ void cURLCamera::Terminate() { /* cURL cleanup */ (*curl_global_cleanup_f)(); - if(curl_lib) + if (curl_lib) dlclose(curl_lib); } int cURLCamera::PrimeCapture() { + getVideoStream(); //Info( "Priming capture from %s", mPath.c_str() ); - return 0; + return 1; } int cURLCamera::PreCapture() { - // Nothing to do here - return( 0 ); + // Nothing to do here + return 1; } -int cURLCamera::Capture( ZMPacket &zm_packet ) { +int cURLCamera::Capture(std::shared_ptr &zm_packet) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -174,10 +195,9 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { /* Grab the mutex to ensure exclusive access to the shared data */ lock(); - while ( !frameComplete ) { - + while (!frameComplete) { /* If the work thread did a reset, reset our local variables */ - if ( bReset ) { + if (bReset) { SubHeadersParsingComplete = false; frame_content_length = 0; frame_content_type.clear(); @@ -185,40 +205,40 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { bReset = false; } - if ( mode == MODE_UNSET ) { + if (mode == MODE_UNSET) { /* Don't have a mode yet. Sleep while waiting for data */ nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); - if ( nRet != 0 ) { + if (nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); return -1; } } - if ( mode == MODE_STREAM ) { + if (mode == MODE_STREAM) { /* Subheader parsing */ - while( !SubHeadersParsingComplete && !need_more_data ) { + while (!SubHeadersParsingComplete && !need_more_data) { size_t crlf_start, crlf_end, crlf_size; std::string subheader; /* Check if the buffer contains something */ - if ( databuffer.empty() ) { + if (databuffer.empty()) { /* Empty buffer, wait for data */ need_more_data = true; break; } - + /* Find crlf start */ - crlf_start = memcspn(databuffer,"\r\n",databuffer.size()); - if ( crlf_start == 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; break; } /* See if we have enough data for determining crlf length */ - if ( databuffer.size() < crlf_start+5 ) { + if (databuffer.size() < crlf_start+5) { /* Need more data */ need_more_data = true; break; @@ -229,18 +249,22 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { crlf_size = (crlf_start + crlf_end) - crlf_start; /* Is this the end of a previous stream? (This is just before the boundary) */ - if ( crlf_start == 0 ) { + if (crlf_start == 0) { databuffer.consume(crlf_size); - continue; + continue; } /* Check for invalid CRLF size */ - if ( crlf_size > 4 ) { + if (crlf_size > 4) { Error("Invalid CRLF length"); } /* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */ - if( (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) || (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) ) { + if ( + (crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0) + || + (crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0) + ) { /* This is the last header */ SubHeadersParsingComplete = true; } @@ -251,69 +275,85 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { /* Advance the buffer past this one */ databuffer.consume(crlf_start+crlf_size); - Debug(7,"Got subheader: %s",subheader.c_str()); + Debug(7, "Got subheader: %s",subheader.c_str()); /* Find where the data in this header starts */ size_t subheader_data_start = subheader.rfind(' '); - if ( subheader_data_start == std::string::npos ) { + if (subheader_data_start == std::string::npos) { subheader_data_start = subheader.find(':'); } /* Extract the data into a string */ std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos); - Debug(8,"Got subheader data: %s",subheader_data.c_str()); + Debug(8, "Got subheader data: %s", subheader_data.c_str()); /* Check the header */ - if(strncasecmp(subheader.c_str(),content_length_match,content_length_match_len) == 0) { + if (strncasecmp(subheader.c_str(), content_length_match, content_length_match_len) == 0) { /* Found the content-length header */ frame_content_length = atoi(subheader_data.c_str()); Debug(6,"Got content-length subheader: %d",frame_content_length); - } else if(strncasecmp(subheader.c_str(),content_type_match,content_type_match_len) == 0) { + } else if (strncasecmp(subheader.c_str(), content_type_match, content_type_match_len) == 0) { /* Found the content-type header */ frame_content_type = subheader_data; - Debug(6,"Got content-type subheader: %s",frame_content_type.c_str()); + Debug(6,"Got content-type subheader: %s", frame_content_type.c_str()); } } /* Attempt to extract the frame */ - if(!need_more_data) { - if(!SubHeadersParsingComplete) { + if (!need_more_data) { + if (!SubHeadersParsingComplete) { /* We haven't parsed all headers yet */ need_more_data = true; - } else if ( ! frame_content_length ) { + } else if (!frame_content_length) { /* Invalid frame */ Error("Invalid frame: invalid content length"); - } else if ( frame_content_type != "image/jpeg" ) { + } else if (frame_content_type != "image/jpeg") { /* Unsupported frame type */ - Error("Unsupported frame: %s",frame_content_type.c_str()); - } else if(frame_content_length > databuffer.size()) { + Error("Unsupported frame: %s", frame_content_type.c_str()); + } else if (frame_content_length > databuffer.size()) { /* Incomplete frame, wait for more data */ need_more_data = true; } else { /* All good. decode the image */ - zm_packet.image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); + if (!zm_packet->image) { + Debug(4, "Allocating image"); + 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; + zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); frameComplete = true; } } /* Attempt to get more data */ - if(need_more_data) { + if (need_more_data) { nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); - if(nRet != 0) { + if (nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); return -1; } need_more_data = false; } - } else if(mode == MODE_SINGLE) { + } else if (mode == MODE_SINGLE) { /* Check if we have anything */ if (!single_offsets.empty()) { - if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { + if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) { /* Extract frame */ - zm_packet.image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); + if (!zm_packet->image) { + Debug(4, "Allocating image"); + 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; + zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); single_offsets.pop_front(); frameComplete = true; } else { @@ -341,15 +381,15 @@ int cURLCamera::Capture( ZMPacket &zm_packet ) { /* Release the mutex */ unlock(); - if(!frameComplete) + if (!frameComplete) return 0; return 1; } int cURLCamera::PostCapture() { - // Nothing to do here - return( 0 ); + // Nothing to do here + return 1; } size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { @@ -360,7 +400,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * /* Signal data available */ int nRet = pthread_cond_signal(&data_available_cond); - if ( nRet != 0 ) { + if (nRet != 0) { Error("Failed signaling data available condition variable: %s",strerror(nRet)); unlock(); return -16; @@ -375,18 +415,18 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { std::string header; header.assign((const char*)buffer, size*nmemb); - - Debug(4,"Got header: %s",header.c_str()); - /* Check Content-Type header */ - if(strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { + Debug(4, "Got header: %s", header.c_str()); + + /* Check Content-Type header */ + if (strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) { size_t pos = header.find(';'); - if(pos != std::string::npos) { + if (pos != std::string::npos) { header.erase(pos, std::string::npos); } pos = header.rfind(' '); - if(pos == std::string::npos) { + if (pos == std::string::npos) { pos = header.find(':'); } @@ -397,17 +437,17 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi const char* multipart_match = "multipart/x-mixed-replace"; const char* image_jpeg_match = "image/jpeg"; - if(strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { - Debug(7,"Content type matched as multipart/x-mixed-replace"); + if (strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) { + Debug(7, "Content type matched as multipart/x-mixed-replace"); mode = MODE_STREAM; - } else if(strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { - Debug(7,"Content type matched as image/jpeg"); + } else if (strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) { + Debug(7, "Content type matched as image/jpeg"); mode = MODE_SINGLE; } unlock(); } - + /* Return bytes processed */ return size*nmemb; } @@ -417,7 +457,7 @@ void* cURLCamera::thread_func() { double dSize; c = (*curl_easy_init_f)(); - if(c == nullptr) { + if (c == nullptr) { dlclose(curl_lib); Error("Failed getting easy handle from libcurl"); tRet = -51; @@ -427,99 +467,99 @@ void* cURLCamera::thread_func() { CURLcode cRet; /* 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)); + if (cRet != CURLE_OK) { + Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet)); tRet = -52; return (void*)tRet; } - + /* Header callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -53; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -54; return (void*)tRet; } /* Data callback */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -55; return (void*)tRet; } cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -56; return (void*)tRet; } /* 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)); + if (cRet != CURLE_OK) { + Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -57; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet)); tRet = -58; return (void*)tRet; } - + cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet)); tRet = -59; return (void*)tRet; } /* Set username and password */ - if(!mUser.empty()) { + if (!mUser.empty()) { cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str()); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet)); } - if(!mPass.empty()) { + if (!mPass.empty()) { cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str()); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet)); } /* Authenication preference */ cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY); - if(cRet != CURLE_OK) + if (cRet != CURLE_OK) Warning("Failed setting libcurl acceptable http authenication methods: %s", (*curl_easy_strerror_f)(cRet)); /* Work loop */ - for(int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { + for (int attempt=1;attempt<=CURL_MAXRETRY;attempt++) { tRet = 0; - while(!bTerminate) { + while (!bTerminate) { /* Do the work */ cRet = (*curl_easy_perform_f)(c); - if(mode == MODE_SINGLE) { - if(cRet != CURLE_OK) { + if (mode == MODE_SINGLE) { + if (cRet != CURLE_OK) { break; } /* Attempt to get the size of the file */ cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize); - if(cRet != CURLE_OK) { + if (cRet != CURLE_OK) { break; } /* We need to lock for the offsets array and the condition variable */ lock(); /* Push the size into our offsets array */ - if(dSize > 0) { + if (dSize > 0) { single_offsets.push_back(dSize); } else { Error("Unable to get the size of the image"); @@ -528,7 +568,7 @@ void* cURLCamera::thread_func() { } /* Signal the request complete condition variable */ tRet = pthread_cond_signal(&request_complete_cond); - if(tRet != 0) { + if (tRet != 0) { Error("Failed signaling request completed condition variable: %s",strerror(tRet)); tRet = -61; return (void*)tRet; @@ -542,13 +582,13 @@ void* cURLCamera::thread_func() { } /* Return value checking */ - if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { + if (cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) { /* Aborted */ break; } else if (cRet != CURLE_OK) { /* Some error */ Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet)); - if(attempt < CURL_MAXRETRY) { + if (attempt < CURL_MAXRETRY) { Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY); /* Do a reset */ lock(); @@ -561,11 +601,11 @@ void* cURLCamera::thread_func() { tRet = -50; } } - + /* Cleanup */ (*curl_easy_cleanup_f)(c); c = nullptr; - + return (void*)tRet; } @@ -593,9 +633,9 @@ int cURLCamera::unlock() { int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { /* Signal the curl thread to terminate */ - if(bTerminate) + if (bTerminate) return -10; - + return 0; } diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index 02174fe33..302d8f877 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -61,21 +61,35 @@ protected: pthread_cond_t request_complete_cond; public: - 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 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( ZMPacket &p ); - int PostCapture(); + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &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 e2b23c7e7..e3b737c07 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -24,7 +24,7 @@ MYSQL dbconn; std::mutex db_mutex; -zmDbQueue dbQueue; +zmDbQueue dbQueue; bool zmDbConnected = false; @@ -102,6 +102,7 @@ bool zmDbConnect() { if ( mysql_query(&dbconn, "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED") ) { Error("Can't set isolation level: %s", mysql_error(&dbconn)); } + mysql_set_character_set(&dbconn, "utf8"); zmDbConnected = true; return zmDbConnected; } @@ -117,25 +118,25 @@ void zmDbClose() { } } -MYSQL_RES * zmDbFetch(const char * query) { +MYSQL_RES *zmDbFetch(const std::string &query) { std::lock_guard lck(db_mutex); if (!zmDbConnected) { Error("Not connected."); return nullptr; } - if (mysql_query(&dbconn, query)) { + if (mysql_query(&dbconn, query.c_str())) { Error("Can't run query: %s", mysql_error(&dbconn)); return nullptr; } MYSQL_RES *result = mysql_store_result(&dbconn); if (!result) { - Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query); + Error("Can't use query result: %s for query %s", mysql_error(&dbconn), query.c_str()); } return result; -} // end MYSQL_RES * zmDbFetch(const char * query); +} -zmDbRow *zmDbFetchOne(const char *query) { +zmDbRow *zmDbFetchOne(const std::string &query) { zmDbRow *row = new zmDbRow(); if (row->fetch(query)) { return row; @@ -144,13 +145,13 @@ zmDbRow *zmDbFetchOne(const char *query) { return nullptr; } -MYSQL_RES *zmDbRow::fetch(const char *query) { +MYSQL_RES *zmDbRow::fetch(const std::string &query) { result_set = zmDbFetch(query); if (!result_set) return result_set; int n_rows = mysql_num_rows(result_set); if (n_rows != 1) { - Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query); + Error("Bogus number of lines return from query, %d returned for query %s.", n_rows, query.c_str()); mysql_free_result(result_set); result_set = nullptr; return result_set; @@ -160,40 +161,65 @@ MYSQL_RES *zmDbRow::fetch(const char *query) { if (!row) { mysql_free_result(result_set); result_set = nullptr; - Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn)); + Error("Error getting row from query %s. Error is %s", query.c_str(), mysql_error(&dbconn)); } else { Debug(5, "Success"); } return result_set; } -int zmDbDo(const char *query) { +int zmDbDo(const std::string &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)); + while ((rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { + Logger *logger = Logger::fetch(); + Logger::Level oldLevel = logger->databaseLevel(); + logger->databaseLevel(Logger::NOLOG); + Error("Can't run query %s: %s", query.c_str(), 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.c_str()); + logger->databaseLevel(oldLevel); return 1; } -int zmDbDoInsert(const char *query) { +int zmDbDoInsert(const std::string &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)); + while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { + Error("Can't run query %s: %s", query.c_str(), 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.c_str(), id); return id; } +int zmDbDoUpdate(const std::string &query) { + std::lock_guard lck(db_mutex); + if (!zmDbConnected) return 0; + int rc; + while ( (rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) { + Error("Can't run query %s: %s", query.c_str(), 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.c_str(), affected); + return affected; +} + zmDbRow::~zmDbRow() { if (result_set) { mysql_free_result(result_set); @@ -208,9 +234,13 @@ zmDbQueue::zmDbQueue() : { } zmDbQueue::~zmDbQueue() { + stop(); +} + +void zmDbQueue::stop() { mTerminate = true; mCondition.notify_all(); - mThread.join(); + if (mThread.joinable()) mThread.join(); } void zmDbQueue::process() { @@ -220,18 +250,40 @@ void zmDbQueue::process() { if (mQueue.empty()) { mCondition.wait(lock); } - if (!mQueue.empty()) { + while (!mQueue.empty()) { + if (mQueue.size() > 10) { + Logger *log = Logger::fetch(); + Logger::Level db_level = log->databaseLevel(); + log->databaseLevel(Logger::NOLOG); + Warning("db queue size has grown larger %zu than 10 entries", mQueue.size()); + log->databaseLevel(db_level); + } std::string sql = mQueue.front(); mQueue.pop(); + // My idea for leaving the locking around each sql statement is to allow + // other db writers to get a chance lock.unlock(); - zmDbDo(sql.c_str()); + zmDbDo(sql); lock.lock(); } } } // end void zmDbQueue::process() void zmDbQueue::push(std::string &&sql) { + if (mTerminate) return; std::unique_lock lock(mMutex); mQueue.push(std::move(sql)); mCondition.notify_all(); } + +std::string zmDbEscapeString(const std::string& to_escape) { + // According to docs, size of safer_whatever must be 2 * length + 1 + // due to unicode conversions + null terminator. + std::string escaped((to_escape.length() * 2) + 1, '\0'); + + + size_t escaped_len = mysql_real_escape_string(&dbconn, &escaped[0], to_escape.c_str(), to_escape.length()); + escaped.resize(escaped_len); + + return escaped; +} diff --git a/src/zm_db.h b/src/zm_db.h index 8ba2df4c7..7343ac75b 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -20,7 +20,6 @@ #ifndef ZM_DB_H #define ZM_DB_H -#include "zm_thread.h" #include #include #include @@ -39,9 +38,9 @@ class zmDbQueue { public: zmDbQueue(); ~zmDbQueue(); - void push(const char *sql) { return push(std::string(sql)); }; void push(std::string &&sql); void process(); + void stop(); }; class zmDbRow { @@ -50,7 +49,7 @@ class zmDbRow { MYSQL_ROW row; public: zmDbRow() : result_set(nullptr), row(nullptr) { }; - MYSQL_RES *fetch(const char *query); + MYSQL_RES *fetch(const std::string &query); zmDbRow(MYSQL_RES *, MYSQL_ROW *row); ~zmDbRow(); @@ -69,10 +68,13 @@ extern bool zmDbConnected; bool zmDbConnect(); void zmDbClose(); -int zmDbDo(const char *query); -int zmDbDoInsert(const char *query); +int zmDbDo(const std::string &query); +int zmDbDoInsert(const std::string &query); +int zmDbDoUpdate(const std::string &query); -MYSQL_RES * zmDbFetch(const char *query); -zmDbRow *zmDbFetchOne(const char *query); +MYSQL_RES * zmDbFetch(const std::string &query); +zmDbRow *zmDbFetchOne(const std::string &query); + +std::string zmDbEscapeString(const std::string& to_escape); #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 index b542351f9..2c23ab3b9 100644 --- a/src/zm_define.h +++ b/src/zm_define.h @@ -29,6 +29,7 @@ #endif #include +#include typedef std::int64_t int64; typedef std::int32_t int32; @@ -39,6 +40,14 @@ typedef std::uint32_t uint32; typedef std::uint16_t uint16; typedef std::uint8_t uint8; -#define SZFMTD "%" PRIuPTR +#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 cd0ec76b4..9f1124e7b 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -26,14 +26,14 @@ #include "zm_monitor.h" #include "zm_signal.h" #include "zm_videostore.h" -#include -#include -//#define USE_PREPARED_SQL 1 +#include +#include +#include +#include const char * Event::frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; -#define MAX_DB_FRAMES 120 -char frame_insert_sql[ZM_SQL_LGE_BUFSIZ] = "INSERT INTO `Frames` (`EventId`, `FrameId`, `Type`, `TimeStamp`, `Delta`, `Score`) VALUES "; +#define MAX_DB_FRAMES 100 int Event::pre_alarm_count = 0; @@ -41,14 +41,14 @@ Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = {}; Event::Event( Monitor *p_monitor, - struct timeval p_start_time, + SystemTimePoint p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) : id(0), monitor(p_monitor), start_time(p_start_time), - end_time({0,0}), + end_time(), cause(p_cause), noteSetMap(p_noteSetMap), frames(0), @@ -60,8 +60,8 @@ Event::Event( //snapshit_file(), //alarm_file(""), videoStore(nullptr), - //video_name(""), //video_file(""), + //video_path(""), last_db_frame(0), have_video_keyframe(false), //scheme @@ -70,49 +70,56 @@ Event::Event( std::string notes; createNotes(notes); - struct timeval now; - gettimeofday(&now, 0); + SystemTimePoint now = std::chrono::system_clock::now(); - if ( !start_time.tv_sec ) { + if (start_time.time_since_epoch() == Seconds(0)) { Warning("Event has zero time, setting to now"); start_time = now; - } else if ( start_time.tv_sec > now.tv_sec ) { + } else if (start_time > now) { char buffer[26]; char buffer_now[26]; - struct tm* tm_info; + tm tm_info = {}; + time_t start_time_t = std::chrono::system_clock::to_time_t(start_time); + time_t now_t = std::chrono::system_clock::to_time_t(now); - tm_info = localtime(&start_time.tv_sec); - strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", tm_info); - tm_info = localtime(&now.tv_sec); - strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", tm_info); + localtime_r(&start_time_t, &tm_info); + strftime(buffer, 26, "%Y:%m:%d %H:%M:%S", &tm_info); + localtime_r(&now_t, &tm_info); + strftime(buffer_now, 26, "%Y:%m:%d %H:%M:%S", &tm_info); - Error( - "StartDateTime in the future starttime %u.%u >? now %u.%u difference %d\n%s\n%s", - start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec, - (now.tv_sec-start_time.tv_sec), - buffer, buffer_now - ); + Error("StartDateTime in the future. Difference: %" PRIi64 " s\nstarttime: %s\nnow: %s", + static_cast(std::chrono::duration_cast(now - start_time).count()), + buffer, buffer_now); start_time = now; } 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]); + } } // 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(); + Storage *storage = monitor->getStorage(); + if (monitor->GetOptVideoWriter() != 0) { + container = monitor->OutputContainer(); + if ( container == "auto" || container == "" ) { + container = "mp4"; + } + video_incomplete_file = "incomplete."+container; + } 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' )", + "( %d, %d, 'New Event', from_unixtime(%" PRId64 "), %u, %u, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )", monitor->Id(), storage->Id(), - start_time.tv_sec, + static_cast(std::chrono::system_clock::to_time_t(start_time)), monitor->Width(), monitor->Height(), cause.c_str(), @@ -120,29 +127,27 @@ Event::Event( state_id, monitor->getOrientation(), 0, - "", + video_incomplete_file.c_str(), save_jpegs, storage->SchemeString().c_str() ); + id = zmDbDoInsert(sql); - id = zmDbDoInsert(sql.c_str()); - - if ( !SetPath(storage) ) { + if (!SetPath(storage)) { // Try another Warning("Failed creating event dir at %s", storage->Path()); sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); - if ( monitor->ServerId() ) + if (monitor->ServerId()) sql += stringtf(" AND ServerId=%u", monitor->ServerId()); - Debug(1, "%s", sql.c_str()); storage = nullptr; - MYSQL_RES *result = zmDbFetch(sql.c_str()); - if ( result ) { - for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + MYSQL_RES *result = zmDbFetch(sql); + if (result) { + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { storage = new Storage(atoi(dbrow[0])); - if ( SetPath(storage) ) + if (SetPath(storage)) break; delete storage; storage = nullptr; @@ -150,18 +155,18 @@ Event::Event( mysql_free_result(result); result = nullptr; } - if ( !storage ) { + 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() ) + if (monitor->ServerId()) sql += stringtf(" OR ServerId != %u", monitor->ServerId()); - MYSQL_RES *result = zmDbFetch(sql.c_str()); - if ( result ) { + result = zmDbFetch(sql); + if (result) { for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { storage = new Storage(atoi(dbrow[0])); - if ( SetPath(storage) ) + if (SetPath(storage)) break; delete storage; storage = nullptr; @@ -170,33 +175,25 @@ Event::Event( result = nullptr; } } - if ( !storage ) { + if (!storage) { storage = new Storage(); Warning("Failed to find a storage area to save events."); } sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); - zmDbDo(sql.c_str()); + zmDbDo(sql); } // end if ! setPath(Storage) Debug(1, "Using storage area at %s", path.c_str()); - video_name = ""; - snapshot_file = path + "/snapshot.jpg"; alarm_file = path + "/alarm.jpg"; - /* Save as video */ + video_incomplete_path = path + "/" + video_incomplete_file; - if ( monitor->GetOptVideoWriter() != 0 ) { - std::string container = monitor->OutputContainer(); - if ( container == "auto" || container == "" ) { - container = "mp4"; - } + if (monitor->GetOptVideoWriter() != 0) { + /* Save as video */ - 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()); videoStore = new VideoStore( - video_file.c_str(), + video_incomplete_path.c_str(), container.c_str(), monitor->GetVideoStream(), monitor->GetVideoCodecContext(), @@ -211,116 +208,114 @@ Event::Event( 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()); + zmDbDo(sql); } } else { - sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id); - zmDbDo(sql.c_str()); + std::string codec = videoStore->get_codec(); + video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); + video_path = path + "/" + video_file; + Debug(1, "Video file is %s", video_file.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 ) + if (storage != monitor->getStorage()) + delete storage; +} 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 ( videoStore != nullptr ) { + if (videoStore != nullptr) { Debug(4, "Deleting video store"); delete videoStore; videoStore = nullptr; + int result = rename(video_incomplete_path.c_str(), video_path.c_str()); + if (result == 0) { + Debug(1, "File successfully renamed"); + } else { + Error("Failed renaming %s to %s", video_incomplete_path.c_str(), video_path.c_str()); + // So that we don't update the event record + video_file = video_incomplete_file; + } } // 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); + if (end_time.time_since_epoch() == Seconds(0)) { + Warning("Empty endtime for event. Should not happen. Setting to now."); + end_time = std::chrono::system_clock::now(); } - 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); - if ( frame_data.size() ) + FPSeconds delta_time = end_time - start_time; + Debug(2, "start_time: %.2f end_time: %.2f", + std::chrono::duration_cast(start_time.time_since_epoch()).count(), + std::chrono::duration_cast(end_time.time_since_epoch()).count()); + + 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 "', 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); - { // scope for lock - std::lock_guard lck(db_mutex); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); - } - if ( !mysql_affected_rows(&dbconn) ) { - // 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); - while ( mysql_query(&dbconn, sql) && !zm_terminate ) { - Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); - } - } // end if no changed rows due to Name change during recording } + + std::string sql = stringtf( + "UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64 " AND Name='New Event'", + monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time), + delta_time.count(), + frames, alarm_frames, + tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, + video_file.c_str(), // defaults to "" + id); + + if (!zmDbDoUpdate(sql)) { + // Name might have been changed during recording, so just do the update without changing the name. + sql = stringtf( + "UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64, + std::chrono::system_clock::to_time_t(end_time), + delta_time.count(), + frames, alarm_frames, + tot_score, static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, + video_file.c_str(), // defaults to "" + id); + zmDbDoUpdate(sql); + } // end if no changed rows due to Name change during recording } // Event::~Event() void Event::createNotes(std::string ¬es) { - if ( !notes.empty() ) { - notes.clear(); - for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); ++mapIter ) { - notes += mapIter->first; - notes += ": "; - const StringSet &stringSet = mapIter->second; - for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); ++setIter ) { - if ( setIter != stringSet.begin() ) - notes += ", "; - notes += *setIter; - } + notes.clear(); + for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); ++mapIter ) { + notes += mapIter->first; + notes += ": "; + const StringSet &stringSet = mapIter->second; + for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); ++setIter ) { + if ( setIter != stringSet.begin() ) + notes += ", "; + notes += *setIter; } - } else { - notes = ""; } } // void Event::createNotes(std::string ¬es) -bool Event::WriteFrameImage( - Image *image, - struct timeval timestamp, - const char *event_file, - bool alarm_frame) const { - +bool Event::WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame) const { int thisquality = (alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ? config.jpeg_alarm_file_quality : 0; // quality to use, zero is default bool rc; - if ( !config.timestamp_on_capture ) { + SystemTimePoint jpeg_timestamp = monitor->Exif() ? timestamp : SystemTimePoint(); + + if (!config.timestamp_on_capture) { // stash the image we plan to use in another pointer regardless if timestamped. // exif is only timestamp at present this switches on or off for write Image *ts_image = new Image(*image); - monitor->TimestampImage(ts_image, ×tamp); - rc = ts_image->WriteJpeg(event_file, thisquality, - (monitor->Exif() ? timestamp : (timeval){0,0})); - delete(ts_image); + monitor->TimestampImage(ts_image, timestamp); + rc = ts_image->WriteJpeg(event_file, thisquality, jpeg_timestamp); + delete ts_image; } else { - rc = image->WriteJpeg(event_file, thisquality, - (monitor->Exif() ? timestamp : (timeval){0,0})); + rc = image->WriteJpeg(event_file, thisquality, jpeg_timestamp); } return rc; -} // end Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) +} -bool Event::WritePacket(ZMPacket &packet) { - - if ( videoStore->writePacket(&packet) < 0 ) +bool Event::WritePacket(const std::shared_ptr&packet) { + if (videoStore->writePacket(packet) < 0) return false; return true; } // bool Event::WriteFrameVideo @@ -329,32 +324,32 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); - if ( newNoteSetMap.size() > 0 ) { - if ( noteSetMap.size() == 0 ) { + if (newNoteSetMap.size() > 0) { + if (noteSetMap.size() == 0) { noteSetMap = newNoteSetMap; update = true; } else { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); + for (StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); - ++newNoteSetMapIter ) { + ++newNoteSetMapIter) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); - if ( newNoteSet.size() > 0 ) { + if (newNoteSet.size() > 0) { StringSetMap::iterator noteSetMapIter = noteSetMap.find(newNoteGroup); - if ( noteSetMapIter == noteSetMap.end() ) { - //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); + if (noteSetMapIter == noteSetMap.end()) { + //Debug(3, "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size()); noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet)); update = true; } else { StringSet ¬eSet = noteSetMapIter->second; - //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); + //Debug(3, "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size()); + for (StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); - ++newNoteSetIter ) { + ++newNoteSetIter) { const std::string &newNote = *newNoteSetIter; StringSet::iterator noteSetIter = noteSet.find(newNote); - if ( noteSetIter == noteSet.end() ) { + if (noteSetIter == noteSet.end()) { noteSet.insert(newNote); update = true; } @@ -365,184 +360,98 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // end if have old notes } // end if have new notes - if ( update ) { + if (update) { std::string notes; createNotes(notes); - Debug(2, "Updating notes for event %d, '%s'", id, notes.c_str()); - static char sql[ZM_SQL_LGE_BUFSIZ]; -#if USE_PREPARED_SQL - static MYSQL_STMT *stmt = 0; + Debug(2, "Updating notes for event %" PRIu64 ", '%s'", id, notes.c_str()); - char notesStr[ZM_SQL_MED_BUFSIZ] = ""; - unsigned long notesLen = 0; - - if ( !stmt ) { - const char *sql = "UPDATE `Events` SET `Notes` = ? WHERE `Id` = ?"; - - stmt = mysql_stmt_init(&dbconn); - if ( mysql_stmt_prepare(stmt, sql, strlen(sql)) ) { - Fatal("Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt)); - } - - /* Get the parameter count from the statement */ - if ( mysql_stmt_param_count(stmt) != 2 ) { - Error("Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count(stmt), sql); - } - - MYSQL_BIND bind[2]; - memset(bind, 0, sizeof(bind)); - - /* STRING PARAM */ - bind[0].buffer_type = MYSQL_TYPE_STRING; - bind[0].buffer = (char *)notesStr; - bind[0].buffer_length = sizeof(notesStr); - bind[0].is_null = 0; - bind[0].length = ¬esLen; - - bind[1].buffer_type= MYSQL_TYPE_LONG; - bind[1].buffer= (char *)&id; - bind[1].is_null= 0; - bind[1].length= 0; - - /* Bind the buffers */ - 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)); - - if ( mysql_stmt_execute(stmt) ) { - Error("Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt)); - } -#else - 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); - zmDbDo(sql); -#endif + std::string sql = stringtf("UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, + zmDbEscapeString(notes).c_str(), id); + dbQueue.push(std::move(sql)); } // 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::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) { - char *frame_insert_values = (char *)&frame_insert_sql + 90; // 90 == strlen(frame_insert_sql); - //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; - } else if ( timestamps[i]->tv_sec < 0 ) { - Warning( "Not adding pre-capture frame %d, negative timestamp", i ); - continue; - } else { - Debug( 3, "Adding pre-capture frame %d, timestamp = (%d), start_time=(%d)", i, timestamps[i]->tv_sec, start_time.tv_sec ); - } - - frames++; - - if ( save_jpegs & 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()); - } - - 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; - } - - frame_insert_values += snprintf(frame_insert_values, - sizeof(frame_insert_sql)-(frame_insert_values-(char *)&frame_insert_sql), - "\n( %" PRIu64 ", %d, 'Normal', from_unixtime(%ld), %s%ld.%02ld, 0 ),", - id, frames, timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec); - - frameCount++; - } // end foreach frame - - if ( frameCount ) { - *(frame_insert_values-1) = '\0'; - zmDbDo(frame_insert_sql); - last_db_frame = frames; - } else { - Debug(1, "No valid pre-capture frames to add"); - } - end_time = *timestamps[n_frames-1]; -} // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) - -void Event::AddPacket(ZMPacket *packet) { - +void Event::AddPacket(const std::shared_ptr&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 ) { + + 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->score, packet->analysis_image); - end_time = *packet->timestamp; - return; + + 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; } void Event::WriteDbFrames() { - char *frame_insert_values_ptr = (char *)&frame_insert_sql + 90; // 90 == strlen(frame_insert_sql); + 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 "; - /* Each frame needs about 63 chars. So if we buffer too many frames, we will exceed the size of frame_insert_sql; - */ - Debug(1, "Inserting %d frames", frame_data.size()); - while ( frame_data.size() ) { + Debug(1, "Inserting %zu frames", frame_data.size()); + while (frame_data.size()) { Frame *frame = frame_data.front(); frame_data.pop(); - frame_insert_values_ptr += snprintf(frame_insert_values_ptr, - sizeof(frame_insert_sql)-(frame_insert_values_ptr-(char *)&frame_insert_sql), - "\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.sec, - frame->delta.fsec, - frame->score); + frame_insert_sql += stringtf("\n( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %.2f, %d ),", + id, frame->frame_id, + frame_type_names[frame->type], + std::chrono::system_clock::to_time_t(frame->timestamp), + std::chrono::duration_cast(frame->delta).count(), + 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_.Lo().x_, + stats.alarm_box_.Lo().y_, + stats.alarm_box_.Hi().x_, + stats.alarm_box_.Hi().y_, + 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); + 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)); } - *(frame_insert_values_ptr-1) = '\0'; // The -1 is for the extra , added for values above - zmDbDo(frame_insert_sql); } // end void Event::WriteDbFrames() -void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { - if (!timestamp.tv_sec) { +void Event::AddFrame(Image *image, + SystemTimePoint timestamp, + const std::vector &zone_stats, + int score, + Image *alarm_image) { + if (timestamp.time_since_epoch() == Seconds(0)) { Warning("Not adding new frame, zero timestamp"); return; } @@ -561,26 +470,28 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a ) ? 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 ( score < 0 ) - score = 0; + frame_type_names[frame_type], score, monitor->GetState(), frames, config.bulk_frame_interval, (frames % config.bulk_frame_interval)); + 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); + std::string event_file = stringtf(staticConfig.capture_file_format.c_str(), 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 + Debug(1, "frames %d, score %d max_score %d", frames, score, max_score); // If this is the first frame, we should add a thumbnail to the event directory - if ( (frames == 1) || (score > (int)max_score) ) { + 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. + Debug(1, "Writing snapshot"); WriteFrameImage(image, timestamp, snapshot_file.c_str()); + } else { + Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score); } // We are writing an Alarm frame @@ -589,12 +500,14 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a 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; + Debug(1, "Writing alarm image"); WriteFrameImage(image, timestamp, alarm_file.c_str()); + } else { + Debug(3, "Not Writing alarm image because alarm frame already written"); } - alarm_frames++; if (alarm_image and (save_jpegs & 2)) { - std::string event_file = stringtf(staticConfig.analyse_file_format, path.c_str(), frames); + std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames); Debug(1, "Writing analysis frame %d", frames); if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { Error("Failed to write analysis frame image"); @@ -605,114 +518,123 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a Debug(1, "No image"); } // end if has image - 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::PREALARM ); - if (db_frame) { + if (frame_type == ALARM) alarm_frames++; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); - Debug(1, "Frame delta is %d.%d - %d.%d = %d.%d", - start_time.tv_sec, start_time.tv_usec, - timestamp.tv_sec, timestamp.tv_usec, - delta_time.sec, delta_time.fsec); + 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 ); + + if (db_frame) { + Microseconds delta_time = std::chrono::duration_cast(timestamp - start_time); + Debug(1, "Frame delta is %.2f s - %.2f s = %.2f s, score %u zone_stats.size %zu", + FPSeconds(timestamp.time_since_epoch()).count(), + FPSeconds(start_time.time_since_epoch()).count(), + FPSeconds(delta_time).count(), + 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)); + 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 %d 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)); + 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; - static 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?"":"-" ), - delta_time.sec, delta_time.fsec, - frames, + std::string sql = stringtf( + "UPDATE Events SET Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64, + FPSeconds(delta_time).count(), + frames, alarm_frames, tot_score, - (int)(alarm_frames?(tot_score/alarm_frames):0), + static_cast(alarm_frames ? (tot_score / alarm_frames) : 0), max_score, - id - ); - zmDbDo(sql); - } else { - Debug(1, "Not Adding %d frames to DB because write_to_db:%d or frames > analysis fps %f or BULK", - frame_data.size(), write_to_db, fps); + id); + 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) + 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 ) ) { + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); return false; } - struct tm *stime = localtime(&start_time.tv_sec); - if ( scheme == Storage::DEEP ) { + time_t start_time_t = std::chrono::system_clock::to_time_t(start_time); + + tm stime = {}; + localtime_r(&start_time_t, &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; + 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++ ) { + 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 ) ) { + 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 ) + if (i == 2) date_path = path; } - time_path = stringtf("%02d/%02d/%02d", stime->tm_hour, stime->tm_min, stime->tm_sec); + 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 ) { + 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 ) { + } else if (scheme == Storage::MEDIUM) { path += stringtf("/%04d-%02d-%02d", - stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday + stime.tm_year+1900, stime.tm_mon+1, stime.tm_mday ); - if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) { + 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 ) ) { + 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 ) ) { + if (mkdir(path.c_str(), 0755) and (errno != EEXIST)) { Error("Can't mkdir %s: %s", path.c_str(), strerror(errno)); return false; } @@ -725,6 +647,6 @@ bool Event::SetPath(Storage *storage) { Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno)); return false; } - } // deep storage or not + } // deep storage or not return true; } // end bool Event::SetPath diff --git a/src/zm_event.h b/src/zm_event.h index 7ba4b03ba..611b2f716 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -20,8 +20,13 @@ #ifndef ZM_EVENT_H #define ZM_EVENT_H +#include "zm_config.h" #include "zm_define.h" #include "zm_storage.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + #include #include #include @@ -65,8 +70,8 @@ class Event { uint64_t id; Monitor *monitor; - struct timeval start_time; - struct timeval end_time; + SystemTimePoint start_time; + SystemTimePoint end_time; std::string cause; StringSetMap noteSetMap; int frames; @@ -79,8 +84,13 @@ class Event { std::string alarm_file; VideoStore *videoStore; - std::string video_name; + std::string container; + std::string codec; std::string video_file; + std::string video_path; + std::string video_incomplete_file; + std::string video_incomplete_path; + 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; @@ -92,12 +102,10 @@ class Event { 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 - ); + Event(Monitor *p_monitor, + SystemTimePoint p_start_time, + const std::string &p_cause, + const StringSetMap &p_noteSetMap); ~Event(); uint64_t Id() const { return id; } @@ -105,50 +113,40 @@ class Event { 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; } + SystemTimePoint StartTime() const { return start_time; } + SystemTimePoint EndTime() const { return end_time; } - void AddPacket(ZMPacket *p); - bool WritePacket(ZMPacket &p); + void AddPacket(const std::shared_ptr &p); + bool WritePacket(const std::shared_ptr &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; + bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const; 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_image=nullptr); + void AddFrame(Image *image, + SystemTimePoint 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); void WriteDbFrames(); bool SetPath(Storage *storage); public: - static const char *getSubPath(struct 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); - return subpath; - } - static const char *getSubPath(time_t *time) { - return Event::getSubPath(localtime(time)); - } + static std::string getSubPath(tm time) { + std::string subpath = stringtf("%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 std::string getSubPath(time_t *time) { + tm time_tm = {}; + localtime_r(time, &time_tm); + return Event::getSubPath(time_tm); + } - const char* getEventFile(void) const { + const char* getEventFile() const { return video_file.c_str(); } @@ -172,7 +170,7 @@ class Event { } static void AddPreAlarmFrame( Image *image, - struct timeval timestamp, + SystemTimePoint timestamp, int score=0, Image *alarm_frame=nullptr ) { diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index da1263384..af9dd8639 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -27,6 +27,7 @@ #include "zm_storage.h" #include #include + #ifdef __FreeBSD__ #include #endif @@ -38,23 +39,17 @@ const std::string EventStream::StreamMode_Strings[4] = { "Gapless" }; -bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { - static char sql[ZM_SQL_SML_BUFSIZ]; +constexpr Milliseconds EventStream::STREAM_PAUSE_WAIT; - snprintf(sql, sizeof(sql), "SELECT `Id` FROM `Events` WHERE " - "`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld " - "ORDER BY `Id` ASC LIMIT 1", monitor_id, event_time); +bool EventStream::loadInitialEventData(int monitor_id, SystemTimePoint event_time) { + std::string sql = stringtf("SELECT `Id` FROM `Events` WHERE " + "`MonitorId` = %d AND unix_timestamp(`EndDateTime`) > %ld " + "ORDER BY `Id` ASC LIMIT 1", monitor_id, std::chrono::system_clock::to_time_t(event_time)); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } + MYSQL_RES *result = zmDbFetch(sql); + 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)); - } MYSQL_ROW dbrow = mysql_fetch_row(result); if ( mysql_errno(&dbconn) ) { @@ -68,23 +63,26 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { loadEventData(init_event_id); - if ( event_time ) { + if (event_time.time_since_epoch() == Seconds(0)) { curr_stream_time = event_time; curr_frame_id = 1; // curr_frame_id is 1-based - if ( event_time >= event_data->start_time ) { + if (event_time >= event_data->start_time) { Debug(2, "event time is after event start"); - for ( unsigned int i = 0; i < event_data->frame_count; i++ ) { + for (unsigned int i = 0; i < event_data->frame_count; i++) { //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); + if (event_data->frames[i].timestamp >= event_time) { + curr_frame_id = i + 1; + Debug(3, "Set curr_stream_time: %.2f, curr_frame_id: %ld", + FPSeconds(curr_stream_time.time_since_epoch()).count(), + 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(std::chrono::duration_cast(event_time.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event_data->start_time.time_since_epoch()).count())); } } // end if have a start time return true; @@ -98,7 +96,7 @@ bool EventStream::loadInitialEventData( 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,27 +111,19 @@ bool EventStream::loadInitialEventData( } bool EventStream::loadEventData(uint64_t event_id) { - static char sql[ZM_SQL_MED_BUFSIZ]; - - snprintf(sql, sizeof(sql), + 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); + 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,11 +140,12 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->monitor_id = atoi(dbrow[0]); 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->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); + event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3]))); + event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : std::chrono::system_clock::now(); + event_data->duration = std::chrono::duration_cast(event_data->end_time - event_data->start_time); + event_data->frames_duration = + std::chrono::duration_cast(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0)); + event_data->video_file = std::string(dbrow[6]); std::string scheme_str = std::string(dbrow[7]); if ( scheme_str == "Deep" ) { event_data->scheme = Storage::DEEP; @@ -185,104 +176,106 @@ 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 = {}; + time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); + localtime_r(&start_time_t, &event_time); - if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), - "%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); - else - snprintf(event_data->path, sizeof(event_data->path), - "%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); - } else if ( event_data->scheme == Storage::MEDIUM ) { - struct tm *event_time = localtime(&event_data->start_time); - if ( storage_path[0] == '/' ) - snprintf(event_data->path, sizeof(event_data->path), - "%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_data->event_id); - else - snprintf(event_data->path, sizeof(event_data->path), - "%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_data->event_id); + if (storage_path[0] == '/') { + event_data->path = stringtf("%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); + } else { + event_data->path = stringtf("%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); + } + } else if (event_data->scheme == Storage::MEDIUM) { + tm event_time = {}; + time_t start_time_t = std::chrono::system_clock::to_time_t(event_data->start_time); + localtime_r(&start_time_t, &event_time); + if (storage_path[0] == '/') { + event_data->path = stringtf("%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_data->event_id); + } else { + event_data->path = stringtf("%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_data->event_id); + } } else { - if ( storage_path[0] == '/' ) - 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/%u/%" PRIu64, - staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, - event_data->event_id); + if (storage_path[0] == '/') { + event_data->path = stringtf("%s/%u/%" PRIu64, storage_path, event_data->monitor_id, event_data->event_id); + } else { + event_data->path = stringtf("%s/%s/%u/%" PRIu64, + staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, + event_data->event_id); + } } - 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)); + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); } + updateFrameRate(fps); - result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %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 = zmDbFetch(sql); + if (!result) { + exit(-1); } event_data->n_frames = mysql_num_rows(result); event_data->frames = new FrameData[event_data->frame_count]; int last_id = 0; - double last_timestamp = event_data->start_time; - double last_delta = 0.0; + SystemTimePoint last_timestamp = event_data->start_time; + Microseconds last_delta = Seconds(0); while ( ( dbrow = mysql_fetch_row(result) ) ) { int id = atoi(dbrow[0]); //timestamp = atof(dbrow[1]); - double delta = atof(dbrow[2]); + Microseconds delta = std::chrono::duration_cast(FPSeconds(atof(dbrow[2]))); int id_diff = id - last_id; - double frame_delta = id_diff ? (delta-last_delta)/id_diff : (delta-last_delta); + Microseconds frame_delta = + std::chrono::duration_cast(id_diff ? (delta - last_delta) / id_diff : (delta - last_delta)); + // Fill in data between bulk frames - if ( id_diff > 1 ) { - for ( int i = last_id+1; i < id; i++ ) { + if (id_diff > 1) { + for (int i = last_id + 1; i < id; i++) { // Delta is the time since last frame, no since beginning of Event - event_data->frames[i-1].delta = frame_delta; - event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta); - event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time; - event_data->frames[i-1].in_db = false; - Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", - i, - event_data->frames[i-1].timestamp, - event_data->frames[i-1].offset, - event_data->frames[i-1].delta, - event_data->frames[i-1].in_db - ); + event_data->frames[i - 1].delta = frame_delta; + event_data->frames[i - 1].timestamp = last_timestamp + ((i - last_id) * frame_delta); + event_data->frames[i - 1].offset = + std::chrono::duration_cast(event_data->frames[i - 1].timestamp - event_data->start_time); + event_data->frames[i - 1].in_db = false; + Debug(3, "Frame %d timestamp (%f s), offset (%f s) delta (%f s), in_db (%d)", + i, + FPSeconds(event_data->frames[i - 1].timestamp.time_since_epoch()).count(), + FPSeconds(event_data->frames[i - 1].offset).count(), + FPSeconds(event_data->frames[i - 1].delta).count(), + event_data->frames[i - 1].in_db); } } - event_data->frames[id-1].timestamp = event_data->start_time + delta; - event_data->frames[id-1].offset = delta; - event_data->frames[id-1].delta = frame_delta; - event_data->frames[id-1].in_db = true; + event_data->frames[id - 1].timestamp = event_data->start_time + delta; + event_data->frames[id - 1].offset = delta; + event_data->frames[id - 1].delta = frame_delta; + event_data->frames[id - 1].in_db = true; last_id = id; last_delta = delta; last_timestamp = event_data->frames[id-1].timestamp; - Debug(3, "Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", - id, - event_data->frames[id-1].timestamp, - event_data->frames[id-1].offset, - event_data->frames[id-1].delta, - event_data->frames[id-1].in_db - ); + Debug(3, "Frame %d timestamp (%f s), offset (%f s), delta(%f s), in_db(%d)", + id, + FPSeconds(event_data->frames[id - 1].timestamp.time_since_epoch()).count(), + FPSeconds(event_data->frames[id - 1].offset).count(), + FPSeconds(event_data->frames[id - 1].delta).count(), + event_data->frames[id - 1].in_db); } // Incomplete events might not have any frame data event_data->last_frame_id = last_id; @@ -293,17 +286,17 @@ bool EventStream::loadEventData(uint64_t event_id) { } mysql_free_result(result); - if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { - if ( !event_data->video_file[0] ) { - snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); + if (!event_data->video_file.empty() || (monitor->GetOptVideoWriter() > 0)) { + if (event_data->video_file.empty()) { + event_data->video_file = stringtf("%" PRIu64 "-%s", event_data->event_id, "video.mp4"); } - std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); + + std::string filepath = event_data->path + "/" + event_data->video_file; Debug(1, "Loading video file from %s", filepath.c_str()); - if ( ffmpeg_input ) - delete ffmpeg_input; + delete ffmpeg_input; ffmpeg_input = new FFmpeg_Input(); - if ( 0 > ffmpeg_input->Open(filepath.c_str()) ) { + if (ffmpeg_input->Open(filepath.c_str()) < 0) { Warning("Unable to open ffmpeg_input %s", filepath.c_str()); delete ffmpeg_input; ffmpeg_input = nullptr; @@ -317,8 +310,12 @@ bool EventStream::loadEventData(uint64_t event_id) { else curr_stream_time = event_data->frames[event_data->last_frame_id-1].timestamp; } - 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); + Debug(2, "Event: %" PRIu64 ", Frames: %ld, Last Frame ID (%ld, Duration: %.2f s Frames Duration: %.2f s", + event_data->event_id, + event_data->frame_count, + event_data->last_frame_id, + FPSeconds(event_data->duration).count(), + FPSeconds(event_data->frames_duration).count()); return true; } // bool EventStream::loadEventData( int event_id ) @@ -333,7 +330,7 @@ void EventStream::processCommand(const CmdMsg *msg) { // Set paused flag paused = true; replay_rate = ZM_RATE_BASE; - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; break; case CMD_PLAY : Debug(1, "Got PLAY command"); @@ -351,7 +348,10 @@ void EventStream::processCommand(const CmdMsg *msg) { curr_frame_id = 1; } else { 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 ); + StreamMode_Strings[(int) mode].c_str(), + curr_frame_id, + event_data->frame_count, + event_data->last_frame_id); } replay_rate = ZM_RATE_BASE; @@ -406,7 +406,7 @@ void EventStream::processCommand(const CmdMsg *msg) { step = 1; 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; @@ -414,7 +414,7 @@ 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"); @@ -517,25 +517,25 @@ void EventStream::processCommand(const CmdMsg *msg) { break; case CMD_SEEK : { - // offset is in seconds + double 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]; + double 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]; - 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 ) { + FPSeconds offset = FPSeconds(int_part + dec_part / 1000000.0); + if (offset < Seconds(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 ) ) { - } + 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 ) { @@ -543,8 +543,10 @@ void EventStream::processCommand(const CmdMsg *msg) { } curr_stream_time = event_data->frames[curr_frame_id-1].timestamp; - Debug(1, "Got SEEK command, to %f (new current frame id: %d offset %f)", - offset, curr_frame_id, event_data->frames[curr_frame_id-1].offset); + Debug(1, "Got SEEK command, to %f s (new current frame id: %ld offset %f s)", + FPSeconds(offset).count(), + curr_frame_id, + FPSeconds(event_data->frames[curr_frame_id - 1].offset).count()); send_frame = true; break; } @@ -561,12 +563,12 @@ void EventStream::processCommand(const CmdMsg *msg) { struct { uint64_t event_id; - double duration; - double progress; + Microseconds duration; + Microseconds progress; int rate; int zoom; bool paused; - } status_data; + } status_data = {}; status_data.event_id = event_data->event_id; status_data.duration = event_data->duration; @@ -575,18 +577,17 @@ void EventStream::processCommand(const CmdMsg *msg) { status_data.zoom = zoom; status_data.paused = paused; 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, - status_data.zoom - ); + status_data.event_id, + FPSeconds(status_data.duration).count(), + status_data.paused, + FPSeconds(status_data.progress).count(), + status_data.rate, + status_data.zoom); 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 ) { @@ -594,53 +595,55 @@ void EventStream::processCommand(const CmdMsg *msg) { //exit(-1); } } - // quit after sending a status, if this was a quit request - if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) - exit(0); - updateFrameRate((event_data->frame_count and event_data->duration) ? (double)event_data->frame_count/event_data->duration : 1); + // quit after sending a status, if this was a quit request + if (static_cast(msg->msg_data[0]) == CMD_QUIT) { + exit(0); + } + + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); + } + updateFrameRate(fps); } // 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->last_frame_id ) { - if ( !event_data->end_time ) { + if (event_data->end_time.time_since_epoch() == Seconds(0)) { // 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; } - snprintf(sql, sizeof(sql), + 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) ) ) { - Debug(1, "Checking for next event %s", sql); - 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); + 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); + Debug(1, "No rows returned for %s", sql.c_str()); } MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -659,10 +662,11 @@ bool EventStream::checkEventLoaded() { 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); + start = std::chrono::system_clock::now(); 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 @@ -684,32 +688,30 @@ bool EventStream::checkEventLoaded() { } // void EventStream::checkEventLoaded() 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); - Image *image = new Image(filepath); + std::string path = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + Debug(2, "EventStream::getImage path(%s) from %s frame(%ld) ", path.c_str(), event_data->path.c_str(), curr_frame_id); + Image *image = new Image(path.c_str()); return image; } -bool EventStream::sendFrame(int delta_us) { - Debug(2, "Sending frame %d", curr_frame_id); +bool EventStream::sendFrame(Microseconds delta_us) { + Debug(2, "Sending frame %ld", curr_frame_id); - static char filepath[PATH_MAX]; - static struct stat filestat; + std::string filepath; + struct stat filestat = {}; // This needs to be abstracted. If we are saving jpgs, then load the capture file. // If we are only saving analysis frames, then send that. - if ( event_data->SaveJPEGs & 1 ) { - snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - } else if ( event_data->SaveJPEGs & 2 ) { - snprintf(filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id); - if ( stat(filepath, &filestat) < 0 ) { - Debug(1, "analyze file %s not found will try to stream from other", filepath); - snprintf(filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id); - if ( stat(filepath, &filestat) < 0 ) { - Debug(1, "capture file %s not found either", filepath); - filepath[0] = 0; + if (event_data->SaveJPEGs & 1) { + filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + } else if (event_data->SaveJPEGs & 2) { + filepath = stringtf(staticConfig.analyse_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + if (stat(filepath.c_str(), &filestat) < 0) { + Debug(1, "analyze file %s not found will try to stream from other", filepath.c_str()); + filepath = stringtf(staticConfig.capture_file_format.c_str(), event_data->path.c_str(), curr_frame_id); + if (stat(filepath.c_str(), &filestat) < 0) { + Debug(1, "capture file %s not found either", filepath.c_str()); + filepath = ""; } } } else if ( !ffmpeg_input ) { @@ -717,9 +719,8 @@ bool EventStream::sendFrame(int delta_us) { return false; } -#if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) { - Image image(filepath); + Image image(filepath.c_str()); Image *send_image = prepareImage(&image); @@ -729,32 +730,30 @@ bool EventStream::sendFrame(int delta_us) { fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } - /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000); - } else -#endif // HAVE_LIBAVCODEC - { - - - bool send_raw = (type == STREAM_JPEG) && ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)) && filepath[0]; + vid_stream->EncodeFrame(send_image->Buffer(), + send_image->Size(), + config.mpeg_timed_frames, + delta_us.count() * 1000); + } else { + bool send_raw = (type == STREAM_JPEG) && ((scale >= ZM_SCALE_BASE) && (zoom == ZM_SCALE_BASE)) && !filepath.empty(); fprintf(stdout, "--" BOUNDARY "\r\n"); - if ( send_raw ) { - if ( !send_file(filepath) ) { - Error("Can't send %s: %s", filepath, strerror(errno)); + if (send_raw) { + if (!send_file(filepath)) { + Error("Can't send %s: %s", filepath.c_str(), strerror(errno)); return false; } } else { Image *image = nullptr; - if ( filepath[0] ) { - image = new Image(filepath); + if (!filepath.empty()) { + image = new Image(filepath.c_str()); } else if ( ffmpeg_input ) { // Get the frame from the mp4 input FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - AVFrame *frame = ffmpeg_input->get_frame( - ffmpeg_input->get_video_stream_id(), - frame_data->offset); + AVFrame *frame = + ffmpeg_input->get_frame(ffmpeg_input->get_video_stream_id(), FPSeconds(frame_data->offset).count()); if ( frame ) { image = new Image(frame); //av_frame_free(&frame); @@ -812,7 +811,7 @@ bool EventStream::sendFrame(int delta_us) { fputs("Content-Type: image/x-rgbz\r\n", stdout); break; case STREAM_RAW : - img_buffer = (uint8_t*)(send_image->Buffer()); + img_buffer = send_image->Buffer(); img_buffer_size = send_image->Size(); fputs("Content-Type: image/x-rgb\r\n", stdout); break; @@ -829,7 +828,7 @@ bool EventStream::sendFrame(int delta_us) { fputs("\r\n", stdout); fflush(stdout); - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } // bool EventStream::sendFrame( int delta_us ) @@ -846,17 +845,21 @@ void EventStream::runStream() { exit(0); } - 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; + double fps = 1.0; + if ((event_data->frame_count and event_data->duration != Seconds(0))) { + fps = static_cast(event_data->frame_count) / FPSeconds(event_data->duration).count(); + } + updateFrameRate(fps); - double time_to_event = 0; + start = std::chrono::system_clock::now(); + + SystemTimePoint::duration last_frame_offset = Seconds(0); + SystemTimePoint::duration time_to_event = Seconds(0); while ( !zm_terminate ) { - gettimeofday(&now, nullptr); + now = std::chrono::system_clock::now(); - int delta_us = 0; + Microseconds delta = Microseconds(0); send_frame = false; if ( connkey ) { @@ -866,7 +869,7 @@ void EventStream::runStream() { } // Update modified time of the socket .lock file so that we can tell which ones are stale. - if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { + if (now - last_comm_update > Hours(1)) { touch(sock_path_lock); last_comm_update = now; } @@ -877,7 +880,7 @@ 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. @@ -892,8 +895,7 @@ 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 time_since_last_send = TV_2_FLOAT(now) - last_frame_sent; - if ( time_since_last_send > MAX_STREAM_DELAY ) { + if (now - last_frame_sent > MAX_STREAM_DELAY) { // Send keepalive Debug(2, "Sending keepalive frame"); send_frame = true; @@ -901,39 +903,47 @@ 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 ) 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]; + if (time_to_event > Seconds(0) and mode == MODE_ALL) { + SystemTimePoint::duration time_since_last_send = now - last_frame_sent; + Debug(1, "Time since last send = %.2f s", FPSeconds(time_since_last_send).count()); + if (time_since_last_send > Seconds(1)) { + char frame_text[64]; - 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) ) + snprintf(frame_text, sizeof(frame_text), "Time to %s event = %f s", + (replay_rate > 0 ? "next" : "previous"), + FPSeconds(time_to_event).count()); + + if (!sendTextFrame(frame_text)) { zm_terminate = true; + } + send_frame = false; // In case keepalive was set } // 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)); + Milliseconds sleep_time = std::chrono::duration_cast( + (replay_rate > 0 ? 1 : -1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT) / ZM_RATE_BASE)); //double sleep_time = (replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); //// ZM_RATE_BASE == 100, and 1x replay_rate is 100 //double sleep_time = ((replay_rate/ZM_RATE_BASE) * STREAM_PAUSE_WAIT)/1000000; - if ( !sleep_time ) { - sleep_time += STREAM_PAUSE_WAIT/1000000; + if (sleep_time == Seconds(0)) { + sleep_time += STREAM_PAUSE_WAIT; } + curr_stream_time += sleep_time; time_to_event -= sleep_time; - Debug(2, "Sleeping (%dus) because we are not at the next event yet, adding %f", STREAM_PAUSE_WAIT, sleep_time); - usleep(STREAM_PAUSE_WAIT); + Debug(2, "Sleeping (%" PRIi64 " ms) because we are not at the next event yet, adding %" PRIi64 " ms", + static_cast(Milliseconds(STREAM_PAUSE_WAIT).count()), + static_cast(Milliseconds(sleep_time).count())); + std::this_thread::sleep_for(STREAM_PAUSE_WAIT); //curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); //} continue; } // end if !in_event - if ( send_frame ) { - if ( !sendFrame(delta_us) ) { + if (send_frame) { + if (!sendFrame(delta)) { zm_terminate = true; break; } @@ -941,23 +951,32 @@ void EventStream::runStream() { curr_stream_time = frame_data->timestamp; - if ( !paused ) { - + if (!paused) { // delta is since the last frame - delta_us = (unsigned int)(frame_data->delta * 1000000); - Debug(3, "frame delta %uus ", delta_us); + delta = std::chrono::duration_cast(frame_data->delta); + Debug(3, "frame delta %" PRIi64 "us ", + static_cast(std::chrono::duration_cast(delta).count())); + // if effective > base we should speed up frame delivery - 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 = 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); + if (base_fps < effective_fps) { + delta = std::chrono::duration_cast((delta * base_fps) / effective_fps); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f)", + static_cast(std::chrono::duration_cast(delta).count()), + base_fps, + effective_fps); + + // but must not exceed maxfps + delta = std::max(delta, Microseconds(lround(Microseconds::period::den / maxfps))); + Debug(3, "delta %" PRIi64 " us = base_fps (%f) / effective_fps (%f) from 30fps", + static_cast(std::chrono::duration_cast(delta).count()), + base_fps, + effective_fps); + } // +/- 1? What if we are skipping frames? curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod; // sending the frame may have taken some time, so reload now - gettimeofday(&now, nullptr); - uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec); + now = std::chrono::system_clock::now(); // we incremented by replay_rate, so might have jumped past frame_count if ( (mode == MODE_SINGLE) && ( @@ -968,8 +987,8 @@ void EventStream::runStream() { ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); curr_frame_id = 1; - // Have to reset start_usec to now when replaying - start_usec = now_usec; + // Have to reset start to now when replaying + start = now; } frame_data = &event_data->frames[curr_frame_id-1]; @@ -981,43 +1000,53 @@ void EventStream::runStream() { // you can calculate the relationship between now and the start // or calc the relationship from the last frame. I think from the start is better as it self-corrects // - if ( last_frame_offset ) { + if (last_frame_offset != Seconds(0)) { // 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); + delta = std::chrono::duration_cast(frame_data->offset - (now - start)); + + Debug(2, "New delta: now - start = %" PRIu64 " us offset %" PRIi64 " us- elapsed = %" PRIu64 " us", + static_cast(std::chrono::duration_cast(now - start).count()), + static_cast(std::chrono::duration_cast(frame_data->offset).count()), + static_cast(std::chrono::duration_cast(delta).count())); } else { Debug(2, "No last frame_offset, no sleep"); - delta_us = 0; + delta = Seconds(0); } - last_frame_offset = frame_data->offset * 1000000; + last_frame_offset = frame_data->offset; - if ( send_frame && (type != STREAM_MPEG) ) { - if ( delta_us > 0 ) { - if ( delta_us > MAX_SLEEP_USEC ) { - Debug(1, "Limiting sleep to %d because calculated sleep is too long %d", MAX_SLEEP_USEC, delta_us); - delta_us = MAX_SLEEP_USEC; + if (send_frame && type != STREAM_MPEG) { + if (delta != Seconds(0)) { + if (delta > MAX_SLEEP) { + Debug(1, "Limiting sleep to %" PRIi64 " ms because calculated sleep is too long: %" PRIi64" us", + static_cast(std::chrono::duration_cast(MAX_SLEEP).count()), + static_cast(std::chrono::duration_cast(delta).count())); + delta = MAX_SLEEP; } - usleep(delta_us); - Debug(3, "Done sleeping: %d usec", delta_us); + + std::this_thread::sleep_for(delta); + Debug(3, "Done sleeping: %" PRIi64 " us", + static_cast(std::chrono::duration_cast(delta).count())); } } } else { - delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); + delta = std::chrono::duration_cast(FPSeconds( + 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)", - delta_us, - ZM_RATE_BASE, - (base_fps ? base_fps : 1), - (replay_rate ? abs(replay_rate*2) : 0) - ); - if ( delta_us > 0 ) { - if ( delta_us > MAX_SLEEP_USEC ) { - Debug(1, "Limiting sleep to %d because calculated sleep is too long %d", MAX_SLEEP_USEC, delta_us); - delta_us = MAX_SLEEP_USEC; + Debug(2, "Sleeping %" PRIi64 " us because ZM_RATE_BASE (%d) / ( base_fps (%f) * replay_rate (%d)", + static_cast(std::chrono::duration_cast(delta).count()), + ZM_RATE_BASE, + (base_fps ? base_fps : 1), + (replay_rate ? abs(replay_rate * 2) : 0)); + + if (delta != Seconds(0)) { + if (delta > MAX_SLEEP) { + Debug(1, "Limiting sleep to %" PRIi64 " ms because calculated sleep is too long %" PRIi64, + static_cast(std::chrono::duration_cast(MAX_SLEEP).count()), + static_cast(std::chrono::duration_cast(delta).count())); + delta = MAX_SLEEP; } - usleep(delta_us); + + std::this_thread::sleep_for(delta); } // We are paused, so might be stepping //if ( step != 0 )// Adding 0 is cheaper than an if 0 @@ -1034,53 +1063,57 @@ void EventStream::runStream() { 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); + Debug(1, "replay rate (%d) time_to_event (%f s) = frame timestamp (%f s) - curr_stream_time (%f s)", + replay_rate, + FPSeconds(time_to_event).count(), + FPSeconds(event_data->frames[0].timestamp.time_since_epoch()).count(), + FPSeconds(curr_stream_time.time_since_epoch()).count()); } 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); + Debug(1, "replay rate (%d), time_to_event(%f s) = curr_stream_time (%f s) - frame timestamp (%f s)", + replay_rate, + FPSeconds(time_to_event).count(), + FPSeconds(curr_stream_time.time_since_epoch()).count(), + FPSeconds(event_data->frames[event_data->frame_count - 1].timestamp.time_since_epoch()).count()); } // end if forward or reverse } // end if checkEventLoaded } // end while ! zm_terminate -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) + if (type == STREAM_MPEG) { delete vid_stream; -#endif // HAVE_LIBAVCODEC + } closeComms(); } // end void EventStream::runStream() -bool EventStream::send_file(const char *filepath) { +bool EventStream::send_file(const std::string &filepath) { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; int img_buffer_size = 0; uint8_t *img_buffer = temp_img_buffer; - FILE *fdj = NULL; - fdj = fopen(filepath, "rb"); + FILE *fdj = nullptr; + fdj = fopen(filepath.c_str(), "rb"); if ( !fdj ) { - Error("Can't open %s: %s", filepath, strerror(errno)); - return false; + Error("Can't open %s: %s", filepath.c_str(), strerror(errno)); + std::string error_message = stringtf("Can't open %s: %s", filepath.c_str(), strerror(errno)); + return sendTextFrame(error_message.c_str()); } #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)); + Error("Failed getting information about file %s: %s", filepath.c_str(), strerror(errno)); return false; } if ( !filestat.st_size ) { fclose(fdj); /* Close the file handle */ - Info("File size is zero. Unable to send raw frame %u: %s", curr_frame_id); + 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; } int rc = zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size); @@ -1089,27 +1122,27 @@ bool EventStream::send_file(const char *filepath) { fclose(fdj); /* Close the file handle */ return true; } - Warning("Unable to send raw frame %u: %s rc %d", curr_frame_id, strerror(errno), rc); + 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); fclose(fdj); /* Close the file handle */ if ( !img_buffer_size ) { - Info("Unable to read raw frame %u: %s", curr_frame_id, strerror(errno)); + Info("Unable to read raw frame %ld: %s", curr_frame_id, strerror(errno)); return false; } 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) { if ( 0 > fprintf(stdout, "Content-Length: %d\r\n\r\n", size) ) { - 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; } int rc = fwrite(buffer, size, 1, stdout); if ( 1 != rc ) { - Error("Unable to send raw frame %u: %s %d", curr_frame_id, strerror(errno), 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 ef873c25d..387748b08 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -26,15 +26,11 @@ #include "zm_storage.h" #include "zm_stream.h" -#ifdef __cplusplus extern "C" { -#endif -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" -#ifdef __cplusplus +#include +#include +#include } -#endif class EventStream : public StreamBase { public: @@ -44,10 +40,10 @@ class EventStream : public StreamBase { protected: struct FrameData { //unsigned long id; - double timestamp; - double offset; - double delta; - bool in_db; + SystemTimePoint timestamp; + Microseconds offset; + Microseconds delta; + bool in_db; }; struct EventData { @@ -56,21 +52,21 @@ class EventStream : public StreamBase { unsigned long storage_id; 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]; + SystemTimePoint start_time; + SystemTimePoint end_time; + Microseconds duration; + Microseconds frames_duration; + std::string path; int n_frames; // # of frame rows returned from database FrameData *frames; - char video_file[PATH_MAX]; + std::string video_file; Storage::Schemes scheme; int SaveJPEGs; Monitor::Orientation Orientation; }; protected: - static const int STREAM_PAUSE_WAIT = 250000; // Microseconds + static constexpr Milliseconds STREAM_PAUSE_WAIT = Milliseconds(250); static const StreamMode DEFAULT_MODE = MODE_SINGLE; @@ -78,35 +74,32 @@ class EventStream : public StreamBase { bool forceEventChange; long curr_frame_id; - double curr_stream_time; + SystemTimePoint curr_stream_time; bool send_frame; - struct timeval start; // clock time when started the event + SystemTimePoint start; // clock time when started the event 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 loadInitialEventData(int monitor_id, SystemTimePoint event_time); bool checkEventLoaded(); void processCommand(const CmdMsg *msg) override; - bool sendFrame(int delta_us); + bool sendFrame(Microseconds delta); public: EventStream() : mode(DEFAULT_MODE), forceEventChange(false), curr_frame_id(0), - curr_stream_time(0.0), 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 ) { if ( event_data->frames ) { @@ -131,12 +124,10 @@ class EventStream : public StreamBase { void runStream() override; Image *getImage(); private: - bool send_file(const char *filepath); + bool send_file(const std::string &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_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 6ff053c34..3986b38b8 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -21,55 +21,51 @@ #include "zm_logger.h" #include "zm_rgb.h" +#include "zm_utils.h" extern "C" { -#include "libavutil/pixdesc.h" +#include } -#if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE - 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]; int length = vsnprintf(logString, sizeof(logString)-1, fmt, vargs); - if ( length > 0 ) { - if ( static_cast(length) > sizeof(logString)-1 ) length = sizeof(logString)-1; + 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, logString); + 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); } @@ -80,8 +76,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"); @@ -102,15 +98,13 @@ void FFMPEGDeInit() { bInit = false; } -#if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; 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; @@ -119,16 +113,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 { @@ -136,70 +128,18 @@ 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; } return pf; } -/* The following is copied directly from newer ffmpeg. */ -#if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) -#else -static int parse_key_value_pair(AVDictionary **pm, const char **buf, - const char *key_val_sep, const char *pairs_sep, - int flags) -{ - char *key = av_get_token(buf, key_val_sep); - char *val = nullptr; - int ret; - if (key && *key && strspn(*buf, key_val_sep)) { - (*buf)++; - val = av_get_token(buf, pairs_sep); - } - - if (key && *key && val && *val) - ret = av_dict_set(pm, key, val, flags); - else - 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) { - if (!str) - return 0; - - /* ignore STRDUP flags */ - flags &= ~(AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); - - while (*str) { - int ret; - if ( (ret = parse_key_value_pair(pm, &str, key_val_sep, pairs_sep, flags)) < 0) - return ret; - - if (*str) - str++; - } - - return 0; -} -#endif -#endif // HAVE_LIBAVUTIL - -#endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE - -#if HAVE_LIBAVUTIL #if LIBAVUTIL_VERSION_CHECK(56, 0, 0, 17, 100) int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ int64_t a, b, this_thing; @@ -224,7 +164,6 @@ simple_round: return av_rescale_q(this_thing, fs_tb, out_tb); } #endif -#endif static void zm_log_fps(double d, const char *postfix) { uint64_t v = lrintf(d * 100); @@ -239,58 +178,76 @@ 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) { - 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, + Debug(1, "Dumping codecpar codec_type %d %s codec_id %d %s codec_tag %" PRIu32 + " width %d height %d bit_rate%" PRIu64 " bpcs %d bprs %d format%d %s" + " extradata:%d:%s profile %d level %d field order %d color_range %d" + " color_primaries %d color_trc %d color_space %d location %d video_delay %d", + static_cast(par->codec_type), av_get_media_type_string(par->codec_type), - par->codec_id, + static_cast(par->codec_id), avcodec_get_name(par->codec_id), par->codec_tag, par->width, par->height, par->bit_rate, + par->bits_per_coded_sample, + par->bits_per_raw_sample, par->format, - (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)) + (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)), + par->extradata_size, ByteArrayToHexString(nonstd::span{ + par->extradata, + static_cast::size_type>(par->extradata_size) + }).c_str(), + par->profile, + par->level, + static_cast(par->field_order), + static_cast(par->color_range), + static_cast(par->color_primaries), + static_cast(par->color_trc), + static_cast(par->color_space), + static_cast(par->chroma_location), + static_cast(par->video_delay) ); } -#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) " - "gop_size %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d", + Debug(1, "Dumping codec_context codec_type %d %s codec_id %d %s width %d height %d timebase %d/%d format %s profile %d level %d " + "gop_size %d has_b_frames %d max_b_frames %d me_cmp %d me_range %d qmin %d qmax %d bit_rate %" PRId64 " extradata:%d:%s", codec->codec_type, + av_get_media_type_string(codec->codec_type), codec->codec_id, avcodec_get_name(codec->codec_id), codec->width, codec->height, codec->time_base.num, codec->time_base.den, -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) (codec->pix_fmt == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name(codec->pix_fmt)), -#else - "unsupported on avconv", -#endif + codec->profile, + codec->level, codec->gop_size, + codec->has_b_frames, codec->max_b_frames, codec->me_cmp, codec->me_range, codec->qmin, - codec->qmax + codec->qmax, + codec->bit_rate, + codec->extradata_size, + ByteArrayToHexString(nonstd::span{ + codec->extradata, + static_cast::size_type>(codec->extradata_size) + }).c_str() ); } /* "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); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) AVCodecParameters *codec = st->codecpar; -#else - AVCodecContext *codec = st->codec; -#endif Debug(1, " Stream #%d:%d", index, i); @@ -305,15 +262,12 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) st->time_base.num, st->time_base.den ); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - Debug(1, "codec: %s", avcodec_get_name(st->codecpar->codec_id)); -#else - char buf[256]; - avcodec_string(buf, sizeof(buf), st->codec, is_output); - Debug(1, "codec: %s", buf); -#endif + Debug(1, "codec: %s %s", + avcodec_get_name(st->codecpar->codec_id), + av_get_media_type_string(st->codecpar->codec_type) + ); - 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; @@ -325,9 +279,12 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, ", SAR %d:%d DAR %d:%d", st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, display_aspect_ratio.num, display_aspect_ratio.den); + } else { + Debug(1, ", SAR %d:%d ", + st->sample_aspect_ratio.num, st->sample_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; @@ -335,9 +292,11 @@ 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 ) { + } else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) { Debug(1, "profile %d channels %d sample_rate %d", codec->profile, codec->channels, codec->sample_rate); + } else { + Debug(1, "Unknown codec type %d", codec->codec_type); } if (st->disposition & AV_DISPOSITION_DEFAULT) @@ -395,137 +354,45 @@ enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat fmt) { } } -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) -#else -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; - dst->pts = src->pts; - dst->dts = src->dts; - dst->duration = src->duration; - dst->stream_index = src->stream_index; - return 0; -} -const char *avcodec_get_name(enum AVCodecID id) { - const AVCodecDescriptor *cd; - if ( id == AV_CODEC_ID_NONE) - return "none"; - cd = avcodec_descriptor_get(id); - if (cd) - return cd->name; - AVCodec *codec; - codec = avcodec_find_decoder(id); - if (codec) - return codec->name; - codec = avcodec_find_encoder(id); - if (codec) - return codec->name; - return "unknown codec"; -} - -void av_packet_rescale_ts( - AVPacket *pkt, - AVRational src_tb, - AVRational dst_tb - ) { - if ( pkt->pts != AV_NOPTS_VALUE) - pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb); - if ( pkt->dts != AV_NOPTS_VALUE) - pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb); - if ( pkt->duration != AV_NOPTS_VALUE) - pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb); -} -#endif - bool is_video_stream(const AVStream * stream) { - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - 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 ) { - #else - if ( stream->codec->codec_type == CODEC_TYPE_VIDEO ) { - #endif - #endif + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 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); - #endif + Debug(2, "Not a video type %d != %d", stream->codecpar->codec_type, AVMEDIA_TYPE_VIDEO); return false; } -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 ); - #else - ( codec_context->codec_type == CODEC_TYPE_VIDEO ); - #endif +bool is_video_context(const AVCodecContext *codec_context) { + return codec_context->codec_type == AVMEDIA_TYPE_VIDEO; } -bool is_audio_stream(const AVStream * stream ) { - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - 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 ) { - #else - if ( stream->codec->codec_type == CODEC_TYPE_AUDIO ) { - #endif - #endif - return true; - } - return false; +bool is_audio_stream(const AVStream *stream) { + return stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; } -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 ); - #else - ( codec_context->codec_type == CODEC_TYPE_AUDIO ); - #endif +bool is_audio_context(const AVCodecContext *codec_context) { + return codec_context->codec_type == AVMEDIA_TYPE_AUDIO; } 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 ret; + if ((ret < 0) and (AVERROR_EOF != ret)) { + Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); } return ret; // 1 or 0 -#else - int got_packet = 0; - int ret = avcodec_encode_audio2(context, &packet, nullptr, &got_packet); - if ( ret < 0 ) { - Error("Error encoding (%d) (%s)", ret, av_err2str(ret)); - return ret; - } - return got_packet; // 1 -#endif } // end int zm_receive_packet(AVCodecContext *context, AVPacket &packet) -int zm_send_packet_receive_frame( - AVCodecContext *context, - AVFrame *frame, - AVPacket &packet) { +int zm_send_packet_receive_frame(AVCodecContext *context, AVFrame *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 { @@ -536,74 +403,41 @@ int zm_send_packet_receive_frame( } // In this api the packet is always consumed, so return packet.bytes return packet.size; -# else - int frameComplete = 0; - 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 ) and frame ) { - Error("Could not send frame (error '%s')", - av_make_error_string(ret).c_str()); - return ret; - } + 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 ) { - // The codec may need more samples than it has, perfectly valid - Debug(2, "Codec not ready to give us a packet"); - return 0; - } 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()); - } - zm_av_packet_unref(&packet); - return ret; - } - #else - int data_present; - 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 ) { - Debug(2, "Not ready to out a frame yet."); - zm_av_packet_unref(&packet); + 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 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()); } - #endif + zm_av_packet_unref(&packet); + return ret; + } return 1; } // end int zm_send_frame_receive_packet -void zm_free_codec( AVCodecContext **ctx ) { - if ( *ctx ) { +void zm_free_codec(AVCodecContext **ctx) { + if (*ctx) { avcodec_close(*ctx); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it avcodec_free_context(ctx); -#endif - *ctx = NULL; - } // end if + *ctx = nullptr; + } } void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRational src_tb, const AVRational dst_tb) { @@ -613,20 +447,8 @@ void zm_packet_copy_rescale_ts(const AVPacket *ipkt, AVPacket *opkt, const AVRat av_packet_rescale_ts(opkt, src_tb, dst_tb); } -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) -int zm_resample_audio( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - AVFrame *in_frame, - AVFrame *out_frame - ) { -#if defined(HAVE_LIBSWRESAMPLE) - if ( in_frame ) { +int zm_resample_audio(SwrContext *resample_ctx, AVFrame *in_frame, AVFrame *out_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", @@ -635,74 +457,30 @@ 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)); -#else -#if defined(HAVE_LIBAVRESAMPLE) - 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 ) { - 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 ) { - 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) != - out_frame->nb_samples) { - Warning("Error reading resampled audio."); - return 0; - } -#endif -#endif + Debug(3, "swr_get_delay %" PRIi64, swr_get_delay(resample_ctx, out_frame->sample_rate)); zm_dump_frame(out_frame, "Out frame after resample"); return 1; } -int zm_resample_get_delay( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - int time_base - ) { -#if defined(HAVE_LIBSWRESAMPLE) +int zm_resample_get_delay(SwrContext *resample_ctx, int time_base) { return swr_get_delay(resample_ctx, time_base); -#else -#if defined(HAVE_LIBAVRESAMPLE) - return avresample_available(resample_ctx); -#endif -#endif } -#endif 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; @@ -712,13 +490,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; } @@ -726,4 +504,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 ec22bfc33..f3a3296a8 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -24,26 +24,19 @@ #include "zm_define.h" extern "C" { - -#ifdef HAVE_LIBSWRESAMPLE - #include "libswresample/swresample.h" -#else - #ifdef HAVE_LIBAVRESAMPLE - #include "libavresample/avresample.h" - #endif -#endif +#include // AVUTIL -#if HAVE_LIBAVUTIL_AVUTIL_H -#include "libavutil/avassert.h" +#include #include #include #include #include -#include "libavutil/audio_fifo.h" -#include "libavutil/imgutils.h" +#include +#include +#include #if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" + #include #endif /* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -55,75 +48,9 @@ extern "C" { ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0) -#include -#else -#include -#endif - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) -#include -#endif -#elif HAVE_FFMPEG_AVUTIL_H -#include -#include -#include -#include -#endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - -#if defined(HAVE_LIBAVUTIL_AVUTIL_H) -#if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) - #define _AVPIXELFORMAT AVPixelFormat -#else - #define _AVPIXELFORMAT PixelFormat - #define AV_PIX_FMT_NONE PIX_FMT_NONE - #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 - #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 - #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 - #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 - #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 - #define AV_PIX_FMT_BGRA PIX_FMT_BGRA - #define AV_PIX_FMT_ARGB PIX_FMT_ARGB - #define AV_PIX_FMT_ABGR PIX_FMT_ABGR - #define AV_PIX_FMT_RGBA PIX_FMT_RGBA - #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 - #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P - #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P - #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P - #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P - #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P - #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P - #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 - #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 - #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 - #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 - #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 - #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE - #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 - #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 - #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE - #define AV_PIX_FMT_NV12 PIX_FMT_NV12 - #define AV_PIX_FMT_NV21 PIX_FMT_NV21 - #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 - #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 - #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE - #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE - #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P - #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P - #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P - //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 - //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 - //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 -#endif -#endif /* HAVE_LIBAVUTIL_AVUTIL_H */ +#define _AVPIXELFORMAT AVPixelFormat // AVCODEC -#if HAVE_LIBAVCODEC_AVCODEC_H #include /* @@ -136,20 +63,9 @@ extern "C" { ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_AVCODEC_H -#include -#endif /* HAVE_LIBAVCODEC_AVCODEC_H */ - -#if defined(HAVE_LIBAVCODEC_AVCODEC_H) -#if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) - #define _AVCODECID AVCodecID -#else - #define _AVCODECID CodecID -#endif -#endif /* HAVE_LIBAVCODEC_AVCODEC_H */ +#define _AVCODECID AVCodecID // AVFORMAT -#if HAVE_LIBAVFORMAT_AVFORMAT_H #include /* LIBAVFORMAT_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -161,28 +77,7 @@ extern "C" { ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_AVFORMAT_H -#include -#endif /* HAVE_LIBAVFORMAT_AVFORMAT_H */ - -// AVDEVICE -#if HAVE_LIBAVDEVICE_AVDEVICE_H -#include - -/* LIBAVDEVICE_VERSION_CHECK checks for the right version of libav and FFmpeg - * a is the major version - * b and c the minor and micro versions of libav - * d and e the minor and micro versions of FFmpeg */ -#define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) - -#elif HAVE_FFMPEG_AVDEVICE_H -#include -#endif /* HAVE_LIBAVDEVICE_AVDEVICE_H */ - // SWSCALE -#if HAVE_LIBSWSCALE_SWSCALE_H #include /* LIBSWSCALE_VERSION_CHECK checks for the right version of libav and FFmpeg @@ -193,51 +88,13 @@ extern "C" { ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) -#elif HAVE_FFMPEG_SWSCALE_H -#include -#endif /* HAVE_LIBSWSCALE_SWSCALE_H */ - -#ifdef __cplusplus } -#endif - -#if ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) - -#if !LIBAVFORMAT_VERSION_CHECK(52, 107, 0, 107, 0) - #if defined(AVIO_WRONLY) - #define AVIO_FLAG_WRITE AVIO_WRONLY - #else - #define AVIO_FLAG_WRITE URL_WRONLY - #endif -#endif /* A single function to initialize ffmpeg, to avoid multiple initializations */ void FFMPEGInit(); void FFMPEGDeInit(); -#if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); -#endif // HAVE_LIBAVUTIL - -#if !LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) -#define AV_CODEC_ID_NONE CODEC_ID_NONE -#define AV_CODEC_ID_PCM_MULAW CODEC_ID_PCM_MULAW -#define AV_CODEC_ID_PCM_ALAW CODEC_ID_PCM_ALAW -#define AV_CODEC_ID_PCM_S16BE CODEC_ID_PCM_S16BE -#define AV_CODEC_ID_QCELP CODEC_ID_QCELP -#define AV_CODEC_ID_MP2 CODEC_ID_MP2 -#define AV_CODEC_ID_MP3 CODEC_ID_MP3 -#define AV_CODEC_ID_MJPEG CODEC_ID_MJPEG -#define AV_CODEC_ID_H261 CODEC_ID_H261 -#define AV_CODEC_ID_MPEG1VIDEO CODEC_ID_MPEG1VIDEO -#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO -#define AV_CODEC_ID_MPEG2TS CODEC_ID_MPEG2TS -#define AV_CODEC_ID_H263 CODEC_ID_H263 -#define AV_CODEC_ID_H264 CODEC_ID_H264 -#define AV_CODEC_ID_MPEG4 CODEC_ID_MPEG4 -#define AV_CODEC_ID_AAC CODEC_ID_AAC -#define AV_CODEC_ID_AMR_NB CODEC_ID_AMR_NB -#endif /* * Some versions of libav does not contain this definition. @@ -250,34 +107,15 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp * C++ friendly version of av_err2str taken from http://libav-users.943685.n4.nabble.com/Libav-user-g-4-7-2-fails-to-compile-av-err2str-td4656417.html. * Newer g++ versions fail with "error: taking address of temporary array" when using native libav version. */ -#ifdef __cplusplus - inline static const std::string av_make_error_string(int errnum) { static char errbuf[AV_ERROR_MAX_STRING_SIZE]; -#if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0) av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); -#else - snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); -#endif return (std::string)errbuf; } #undef av_err2str #define av_err2str(errnum) av_make_error_string(errnum).c_str() - /* The following is copied directly from newer ffmpeg */ - #if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) - #else - int av_dict_parse_string(AVDictionary **pm, const char *str, - const char *key_val_sep, const char *pairs_sep, - int flags); - #endif - -#endif // __cplusplus - - -#endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) - #ifndef av_rescale_delta /** * Rescale a timestamp while preserving known durations. @@ -305,23 +143,19 @@ static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, in void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); 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 " keyframe: %d", \ text, \ frame->format, \ @@ -333,23 +167,10 @@ void zm_dump_codecpar(const AVCodecParameters *par); frame->key_frame \ ); -#else -#define zm_dump_video_frame(frame,text) Debug(1, "%s: format %d %s %dx%d linesize:%dx%d pts: %" PRId64, \ - text, \ - frame->format, \ - "unsupported", \ - frame->width, \ - frame->height, \ - frame->linesize[0], frame->linesize[1], \ - frame->pts \ - ); -#endif - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) # define AV_PACKET_DURATION_FMT PRId64 -#else -# define AV_PACKET_DURATION_FMT "d" -#endif + +#define CODEC_TYPE(stream) stream->codecpar->codec_type +#define CODEC(stream) stream->codecpar #ifndef DBG_OFF # define ZM_DUMP_PACKET(pkt, text) \ @@ -370,7 +191,7 @@ void zm_dump_codecpar(const AVCodecParameters *par); 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, flags: %04x, keyframe(%d) pos: %" PRId64", duration: %" AV_PACKET_DURATION_FMT, \ + ", size: %d, stream_index: %d, %s flags: %04x, keyframe(%d) pos: %" PRId64", duration: %" AV_PACKET_DURATION_FMT, \ text, \ pkt.pts, \ stream->time_base.num, \ @@ -379,10 +200,11 @@ void zm_dump_codecpar(const AVCodecParameters *par); 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) \ + pkt.duration); \ } #else @@ -390,39 +212,10 @@ void zm_dump_codecpar(const AVCodecParameters *par); # 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) -#else - unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); - #define zm_av_packet_unref( packet ) av_free_packet( packet ) - const char *avcodec_get_name(AVCodecID id); +#define zm_av_packet_unref(packet) av_packet_unref(packet) +#define zm_av_packet_ref(dst, src) av_packet_ref(dst, src) - void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb); -#endif -#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 -#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 - #define zm_av_frame_alloc() avcodec_alloc_frame() -#endif - -#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - #define av_frame_free( input_avframe ) av_freep( input_avframe ) -#endif +#define zm_av_frame_alloc() av_frame_alloc() int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); enum AVPixelFormat fix_deprecated_pix_fmt(enum AVPixelFormat ); @@ -439,30 +232,8 @@ int zm_send_frame_receive_packet(AVCodecContext *context, AVFrame *frame, AVPack 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) -int zm_resample_audio( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - AVFrame *in_frame, - AVFrame *out_frame - ); -int zm_resample_get_delay( -#if defined(HAVE_LIBSWRESAMPLE) - SwrContext *resample_ctx, -#else -#if defined(HAVE_LIBAVRESAMPLE) - AVAudioResampleContext *resample_ctx, -#endif -#endif - int time_base - ); - -#endif +int zm_resample_audio(SwrContext *resample_ctx, AVFrame *in_frame, AVFrame *out_frame); +int zm_resample_get_delay(SwrContext *resample_ctx, int time_base); int zm_add_samples_to_fifo(AVAudioFifo *fifo, AVFrame *frame); int zm_get_samples_from_fifo(AVAudioFifo *fifo, AVFrame *frame); diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index bd67d5c8c..b122e0f2b 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -19,22 +19,17 @@ #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 - extern "C" { -#include "libavutil/time.h" -#if HAVE_LIBAVUTIL_HWCONTEXT_H - #include "libavutil/hwcontext.h" -#endif - -#include "libavutil/pixdesc.h" +#include } -#include +TimePoint start_read_time; #if HAVE_LIBAVUTIL_HWCONTEXT_H #if LIBAVCODEC_VERSION_CHECK(57, 89, 0, 89, 0) @@ -60,31 +55,26 @@ static enum AVPixelFormat get_hw_format( } #if !LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) { - enum AVPixelFormat fmt; switch (type) { case AV_HWDEVICE_TYPE_VAAPI: - fmt = AV_PIX_FMT_VAAPI; - break; + return AV_PIX_FMT_VAAPI; case AV_HWDEVICE_TYPE_DXVA2: - fmt = AV_PIX_FMT_DXVA2_VLD; - break; + return AV_PIX_FMT_DXVA2_VLD; case AV_HWDEVICE_TYPE_D3D11VA: - fmt = AV_PIX_FMT_D3D11; - break; + return AV_PIX_FMT_D3D11; case AV_HWDEVICE_TYPE_VDPAU: - fmt = AV_PIX_FMT_VDPAU; - break; + return AV_PIX_FMT_VDPAU; case AV_HWDEVICE_TYPE_CUDA: - fmt = AV_PIX_FMT_CUDA; - break; + return AV_PIX_FMT_CUDA; +#ifdef AV_HWDEVICE_TYPE_MMAL + case AV_HWDEVICE_TYPE_MMAL: + return AV_PIX_FMT_MMAL; +#endif case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: - fmt = AV_PIX_FMT_VIDEOTOOLBOX; - break; + return AV_PIX_FMT_VIDEOTOOLBOX; default: - fmt = AV_PIX_FMT_NONE; - break; + return AV_PIX_FMT_NONE; } - return fmt; } #endif #endif @@ -93,6 +83,7 @@ static enum AVPixelFormat find_fmt_by_hw_type(const enum AVHWDeviceType type) { FfmpegCamera::FfmpegCamera( 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, @@ -121,6 +112,7 @@ FfmpegCamera::FfmpegCamera( p_record_audio ), mPath(p_path), + mSecondPath(p_second_path), mMethod(p_method), mOptions(p_options), hwaccel_name(p_hwaccel_name), @@ -141,9 +133,7 @@ FfmpegCamera::FfmpegCamera( #endif #endif -#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 ) { @@ -168,6 +158,7 @@ FfmpegCamera::~FfmpegCamera() { } int FfmpegCamera::PrimeCapture() { + start_read_time = std::chrono::steady_clock::now(); if ( mCanCapture ) { Debug(1, "Priming capture from %s, Closing", mPath.c_str()); Close(); @@ -183,16 +174,37 @@ int FfmpegCamera::PreCapture() { return 0; } -int FfmpegCamera::Capture(ZMPacket &zm_packet) { - if ( !mCanCapture ) - return -1; +int FfmpegCamera::Capture(std::shared_ptr &zm_packet) { + if (!mCanCapture) return -1; + start_read_time = std::chrono::steady_clock::now(); int ret; + AVFormatContext *formatContextPtr; - if ( (ret = av_read_frame(mFormatContext, &packet)) < 0 ) { + 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 || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + (ret == AVERROR_EOF || (formatContextPtr->pb && formatContextPtr->pb->eof_reached)) || // Check for Connection failure. (ret == -110) ) { @@ -204,16 +216,31 @@ int FfmpegCamera::Capture(ZMPacket &zm_packet) { } return -1; } - ZM_DUMP_STREAM_PACKET(mFormatContext->streams[packet.stream_index], packet, "ffmpeg_camera in"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - zm_packet.codec_type = mFormatContext->streams[packet.stream_index]->codecpar->codec_type; -#else - zm_packet.codec_type = mFormatContext->streams[packet.stream_index]->codec->codec_type; -#endif + AVStream *stream = formatContextPtr->streams[packet.stream_index]; + ZM_DUMP_STREAM_PACKET(stream, packet, "ffmpeg_camera in"); + + zm_packet->codec_type = stream->codecpar->codec_type; + bytes += packet.size; - zm_packet.set_packet(&packet); + 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; + + mLastVideoPTS = packet.pts - mFirstVideoPTS; + } else { + if (mFirstAudioPTS == AV_NOPTS_VALUE) + mFirstAudioPTS = packet.pts; + + mLastAudioPTS = packet.pts - mFirstAudioPTS; + } + } zm_av_packet_unref(&packet); + return 1; } // FfmpegCamera::Capture @@ -227,10 +254,6 @@ int FfmpegCamera::OpenFfmpeg() { 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 ) -#else // Handle options AVDictionary *opts = nullptr; ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0); @@ -240,7 +263,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); + protocol = StringToUpper(protocol); if ( protocol == "RTSP" ) { const std::string method = Method(); if ( method == "rtpMulti" ) { @@ -258,7 +281,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()); @@ -272,18 +294,14 @@ int FfmpegCamera::OpenFfmpeg() { ret = avformat_open_input(&mFormatContext, mPath.c_str(), nullptr, &opts); if ( ret != 0 ) -#endif { Error("Unable to open input %s due to: %s", mPath.c_str(), av_make_error_string(ret).c_str()); -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(mFormatContext); -#else + if ( mFormatContext ) { avformat_close_input(&mFormatContext); mFormatContext = nullptr; } -#endif av_dict_free(&opts); return -1; @@ -295,11 +313,8 @@ 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 ret = avformat_find_stream_info(mFormatContext, nullptr); -#endif + if ( ret < 0 ) { Error("Unable to find stream info from %s due to: %s", mPath.c_str(), av_make_error_string(ret).c_str()); @@ -310,10 +325,10 @@ int FfmpegCamera::OpenFfmpeg() { // The one we want Might not be the first mVideoStreamId = -1; mAudioStreamId = -1; - for ( unsigned int i=0; i < mFormatContext->nb_streams; i++ ) { + for (unsigned int i=0; i < mFormatContext->nb_streams; i++) { AVStream *stream = mFormatContext->streams[i]; - if ( is_video_stream(stream) ) { - if ( mVideoStreamId == -1 ) { + 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 @@ -321,8 +336,8 @@ int FfmpegCamera::OpenFfmpeg() { } else { Debug(2, "Have another video stream."); } - } else if ( is_audio_stream(stream) ) { - if ( mAudioStreamId == -1 ) { + } else if (is_audio_stream(stream)) { + if (mAudioStreamId == -1) { mAudioStreamId = i; mAudioStream = mFormatContext->streams[i]; } else { @@ -331,7 +346,7 @@ int FfmpegCamera::OpenFfmpeg() { } } // end foreach stream - if ( mVideoStreamId == -1 ) { + if (mVideoStreamId == -1) { Error("Unable to locate video stream in %s", mPath.c_str()); return -1; } @@ -340,60 +355,44 @@ int FfmpegCamera::OpenFfmpeg() { mVideoStreamId, mAudioStreamId); AVCodec *mVideoCodec = nullptr; - if ( mVideoStream-> -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codecpar -#else - codec -#endif - ->codec_id == AV_CODEC_ID_H264 ) { - if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == nullptr ) { + if (mVideoStream->codecpar->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 { Debug(1, "Success finding decoder (h264_mmal)"); } } - if ( !mVideoCodec ) { - mVideoCodec = avcodec_find_decoder(mVideoStream-> -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codecpar -#else - codec -#endif - ->codec_id); - if ( !mVideoCodec ) { + if (!mVideoCodec) { + mVideoCodec = avcodec_find_decoder(mVideoStream->codecpar->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()); return -1; } } -#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 + avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); + #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 (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) + // 3.2 doesn't seem to have all the bits in place, so let's require 3.4 and up + #if LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) // Print out available types enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; - while ( (type = av_hwdevice_iterate_types(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)); const char *hw_name = hwaccel_name.c_str(); type = av_hwdevice_find_type_by_name(hw_name); - if ( type == AV_HWDEVICE_TYPE_NONE ) { + if (type == AV_HWDEVICE_TYPE_NONE) { Debug(1, "Device type %s is not supported.", hw_name); } else { Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type)); @@ -401,14 +400,14 @@ int FfmpegCamera::OpenFfmpeg() { #if LIBAVUTIL_VERSION_CHECK(56, 22, 0, 14, 0) // Get hw_pix_fmt - for ( int i = 0;; i++ ) { + for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); - if ( !config ) { + if (!config) { Debug(1, "Decoder %s does not support config %d.", mVideoCodec->name, i); break; } - if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) + if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) && (config->device_type == type) ) { hw_pix_fmt = config->pix_fmt; @@ -427,7 +426,7 @@ int FfmpegCamera::OpenFfmpeg() { #else hw_pix_fmt = find_fmt_by_hw_type(type); #endif - if ( hw_pix_fmt != AV_PIX_FMT_NONE ) { + 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)); @@ -440,7 +439,7 @@ int FfmpegCamera::OpenFfmpeg() { if ( ret < 0 and hwaccel_device != "" ) { ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0); } - if ( ret < 0 ) { + if (ret < 0) { Error("Failed to create hwaccel device. %s", av_make_error_string(ret).c_str()); hw_pix_fmt = AV_PIX_FMT_NONE; } else { @@ -459,57 +458,47 @@ int FfmpegCamera::OpenFfmpeg() { #endif } // end if hwaccel_name -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - ret = avcodec_open(mVideoCodecContext, mVideoCodec); -#else ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); -#endif + e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Warning("Option %s not recognized by ffmpeg", e->key); } - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to open codec for video stream from %s", mPath.c_str()); av_dict_free(&opts); return -1; } zm_dump_codec(mVideoCodecContext); + 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 -#else - mFormatContext->streams[mAudioStreamId]->codec->codec_id -#endif - )) == nullptr ) { + if (!(mAudioCodec = avcodec_find_decoder(mAudioStream->codecpar->codec_id))) { Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); } else { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); - avcodec_parameters_to_context( - mAudioCodecContext, - mFormatContext->streams[mAudioStreamId]->codecpar - ); -#else - mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; - // = avcodec_alloc_context3(mAudioCodec); -#endif + avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar); - 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 ) -#else - if ( avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0 ) -#endif - { + if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr) < 0) { Error("Unable to open codec for audio stream from %s", mPath.c_str()); return -1; - } // end if opened - } // end if found decoder - } // end if have audio stream + } // end if opened + } // end if found decoder + } // end if mAudioStreamId if ( ((unsigned int)mVideoCodecContext->width != width) @@ -530,16 +519,12 @@ int FfmpegCamera::Close() { if ( mVideoCodecContext ) { avcodec_close(mVideoCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&mVideoCodecContext); -#endif mVideoCodecContext = nullptr; // Freed by av_close_input_file } if ( mAudioCodecContext ) { avcodec_close(mAudioCodecContext); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&mAudioCodecContext); -#endif mAudioCodecContext = nullptr; // Freed by av_close_input_file } @@ -550,11 +535,7 @@ int FfmpegCamera::Close() { #endif if ( mFormatContext ) { -#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) - av_close_input_file(mFormatContext); -#else avformat_close_input(&mFormatContext); -#endif mFormatContext = nullptr; } @@ -562,9 +543,17 @@ int FfmpegCamera::Close() { } // end FfmpegCamera::Close int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { - // FfmpegCamera* camera = reinterpret_cast(ctx); - // Debug(4, "FfmpegInterruptCallback"); - return zm_terminate; -} + if (zm_terminate) { + Debug(1, "Received terminate in cb"); + return zm_terminate; + } -#endif // HAVE_LIBAVFORMAT + TimePoint now = std::chrono::steady_clock::now(); + if (now - start_read_time > Seconds(10)) { + Debug(1, "timeout in ffmpeg camera now %" PRIi64 " - %" PRIi64 " > 10 s", + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(start_read_time.time_since_epoch()).count())); + return 1; + } + return 0; +} diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index bac1c42d3..e320d3b31 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -22,6 +22,8 @@ #include "zm_camera.h" +#include + #if HAVE_LIBAVUTIL_HWCONTEXT_H typedef struct DecodeContext { AVBufferRef *hw_device_ref; @@ -34,6 +36,7 @@ typedef struct DecodeContext { class FfmpegCamera : public Camera { protected: std::string mPath; + std::string mSecondPath; std::string mMethod; std::string mOptions; @@ -42,7 +45,7 @@ class FfmpegCamera : public Camera { std::string hwaccel_device; int frameCount; - + _AVPIXELFORMAT imagePixFormat; bool use_hwaccel; //will default to on if hwaccel specified, will get turned off if there is a failure @@ -56,12 +59,10 @@ class FfmpegCamera : public Camera { AVPacket packet; int OpenFfmpeg(); - int Close(); + int Close() override; bool mCanCapture; -#if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; -#endif int error_count; @@ -69,6 +70,7 @@ class FfmpegCamera : public Camera { FfmpegCamera( 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, @@ -89,10 +91,10 @@ class FfmpegCamera : public Camera { const std::string &Options() const { return mOptions; } const std::string &Method() const { return mMethod; } - int PrimeCapture(); - int PreCapture(); - int Capture(ZMPacket &p); - int PostCapture(); + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; private: static int FfmpegInterruptCallback(void*ctx); }; diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 2b3bcfd74..17ce88eab 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -89,12 +89,8 @@ int FFmpeg_Input::Open(const char *filepath) { } streams[i].frame_count = 0; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) streams[i].context = avcodec_alloc_context3(nullptr); 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"); @@ -108,9 +104,7 @@ int FFmpeg_Input::Open(const char *filepath) { if ( error < 0 ) { Error("Could not open input codec (error '%s')", 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); input_format_context = nullptr; return error; @@ -118,32 +112,26 @@ 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; @@ -218,21 +206,21 @@ 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) @@ -243,10 +231,10 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { ) { 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 @@ -281,4 +269,4 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { } return get_frame(stream_id); -} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at) +} diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index bd368bf62..6ccc87682 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -3,17 +3,11 @@ #include "zm_define.h" -#ifdef __cplusplus extern "C" { -#endif - -#include "libavformat/avformat.h" -#include "libavformat/avio.h" -#include "libavcodec/avcodec.h" - -#ifdef __cplusplus +#include +#include +#include } -#endif class FFmpeg_Input { @@ -36,6 +30,13 @@ class FFmpeg_Input { 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 index b031399b9..ec677f265 100644 --- a/src/zm_ffmpeg_output.cpp +++ b/src/zm_ffmpeg_output.cpp @@ -52,12 +52,8 @@ int FFmpeg_Output::Open( const char *filepath ) { } 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 + streams[i].context = avcodec_alloc_context3(nullptr); + avcodec_parameters_to_context(streams[i].context, input_format_context->streams[i]->codecpar); if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) { Error( "Could not find input codec\n"); @@ -70,9 +66,7 @@ int FFmpeg_Output::Open( const char *filepath ) { 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; } @@ -117,7 +111,6 @@ AVFrame *FFmpeg_Output::get_frame( int stream_id ) { 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 ); @@ -160,15 +153,6 @@ AVFrame *FFmpeg_Output::get_frame( int stream_id ) { #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 ); diff --git a/src/zm_ffmpeg_output.h b/src/zm_ffmpeg_output.h index 76afad0d7..9ab4ea403 100644 --- a/src/zm_ffmpeg_output.h +++ b/src/zm_ffmpeg_output.h @@ -1,17 +1,11 @@ #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 +#include +#include +#include } -#endif class FFmpeg_Output { diff --git a/src/zm_fifo.cpp b/src/zm_fifo.cpp index 3e09f8886..a999adc23 100644 --- a/src/zm_fifo.cpp +++ b/src/zm_fifo.cpp @@ -24,248 +24,159 @@ #include #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] = ""; +#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; -} +void Fifo::file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo) { + struct stat st = {}; -int zmFifoDbgInit(Monitor *monitor) { - zm_fifodbg_inited = true; - snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/dbgpipe-%d.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); - } -} - -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; -} - -void FifoStream::file_create_if_missing( - const char * path, - bool is_fifo, - bool delete_fake_fifo - ) { - static struct stat st; - if ( stat(path, &st) == 0 ) { - if ( (!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo ) + if (stat(path.c_str(), &st) == 0) { + if ((!is_fifo) || S_ISFIFO(st.st_mode) || !delete_fake_fifo) return; - Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path); - unlink(path); + Debug(5, "Supposed to be a fifo pipe but isn't, unlinking: %s", path.c_str()); + unlink(path.c_str()); } - int fd; - 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); + if (!is_fifo) { + Debug(5, "Creating non fifo file as requested: %s", path.c_str()); + int fd = ::open(path.c_str(), 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); + Debug(5, "Making fifo file of: %s", path.c_str()); + mkfifo(path.c_str(), S_IRUSR | S_IWUSR); } -void FifoStream::fifo_create_if_missing( - const char * path, - bool delete_fake_fifo - ) { +void Fifo::fifo_create_if_missing(const std::string &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); + + 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, - "--" 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; +#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]; - std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); +bool Fifo::writePacket(const ZMPacket &packet) { + if (!(outfile or open())) return false; - if ( !strcmp(format, "reference") ) { - snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-r-%d.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-%d.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-%d.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); - 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; + 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 %zu %" PRId64, bytes, pts); + if (fprintf(outfile, "ZM %zu %" 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 e99d56dd2..5a660c6bb 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 @@ -20,59 +20,41 @@ #define ZM_FIFO_H #include "zm_stream.h" +#include "zm_packet.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); - -class FifoStream : public StreamBase { +class Fifo { 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 {} + std::string path; + bool on_blocking_abort; + FILE *outfile; + int raw_fd; public: - FifoStream() : - stream_path(nullptr), - total_read(0), - bytes_read(0), - frame_count(0), - stream_type(UNKNOWN) + static void file_create_if_missing(const std::string &path, bool is_fifo, bool delete_fake_fifo = true); + static void fifo_create_if_missing(const std::string &path, bool delete_fake_fifo = true); + + Fifo() : + on_blocking_abort(true), + outfile(nullptr), + raw_fd(-1) {} - 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() override; + 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 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..1b1950e3c --- /dev/null +++ b/src/zm_fifo_debug.cpp @@ -0,0 +1,98 @@ +// +// 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; +std::string zm_fifodbg_log; + +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.c_str(), 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; + zm_fifodbg_log = stringtf("%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_box.cpp b/src/zm_fifo_debug.h similarity index 53% rename from src/zm_box.cpp rename to src/zm_fifo_debug.h index 6129b7a29..08f4c4fb5 100644 --- a/src/zm_box.cpp +++ b/src/zm_fifo_debug.h @@ -1,22 +1,42 @@ // -// ZoneMinder Box Class Implementation, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// +// 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 -#include "zm_box.h" +class Monitor; -// This section deliberately left blank +#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..51d88b663 --- /dev/null +++ b/src/zm_fifo_stream.cpp @@ -0,0 +1,171 @@ +// +// 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.c_str(), O_RDONLY); + if ( fd < 0 ) { + Error("Can't open %s: %s", stream_path.c_str(), 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.c_str(), O_RDONLY); + if ( fd < 0 ) { + Error("Can't open %s: %s", stream_path.c_str(), 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 = now; + frame_count++; + return true; +} + +void FifoStream::setStreamStart(const std::string &path) { + stream_path = path; +} + +void FifoStream::setStreamStart(int monitor_id, const char *format) { + std::string diag_path; + std::shared_ptr monitor = Monitor::Load(monitor_id, false, Monitor::QUERY); + + if (!strcmp(format, "reference")) { + diag_path = stringtf("%s/diagpipe-r-%u.jpg", staticConfig.PATH_SOCKS.c_str(), monitor->Id()); + stream_type = MJPEG; + } else if (!strcmp(format, "delta")) { + diag_path = stringtf("%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"); + } + + diag_path = stringtf("%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 */ + std::string lock_file = stringtf("%s.rlock", stream_path.c_str()); + Fifo::file_create_if_missing(lock_file, false); + Debug(1, "Locking %s", lock_file.c_str()); + + int fd_lock = open(lock_file.c_str(), O_RDONLY); + if (fd_lock < 0) { + Error("Can't open %s: %s", lock_file.c_str(), 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.c_str(), 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.c_str(), strerror(errno)); + close(fd_lock); + return; + } + + while (!zm_terminate) { + now = std::chrono::system_clock::now(); + 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..911980d48 --- /dev/null +++ b/src/zm_fifo_stream.h @@ -0,0 +1,52 @@ +// +// 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: + std::string stream_path; + int total_read; + int bytes_read; + unsigned int frame_count; + + protected: + typedef enum { UNKNOWN, MJPEG, RAW } StreamType; + StreamType stream_type; + bool sendMJEGFrames(); + bool sendRAWFrames(); + void processCommand(const CmdMsg *msg) override {} + + public: + FifoStream() : + total_read(0), + bytes_read(0), + frame_count(0), + stream_type(UNKNOWN) + {} + + void setStreamStart(const std::string &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 24b9e74ca..fd07515ad 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -48,20 +48,20 @@ FileCamera::FileCamera( p_capture, p_record_audio) { - strncpy( path, p_path, sizeof(path)-1 ); - if ( capture ) { + path = std::string(p_path); + if (capture) { Initialise(); } } FileCamera::~FileCamera() { - if ( capture ) { + if (capture) { Terminate(); } } void FileCamera::Initialise() { - if ( !path[0] ) { + if (path.empty()) { Fatal("No path specified for file image"); } } @@ -70,9 +70,9 @@ void FileCamera::Terminate() { } int FileCamera::PreCapture() { - struct stat statbuf; - if ( stat(path, &statbuf) < 0 ) { - Error("Can't stat %s: %s", path, strerror(errno)); + struct stat statbuf = {}; + if (stat(path.c_str(), &statbuf) < 0) { + Error("Can't stat %s: %s", path.c_str(), strerror(errno)); return -1; } bytes += statbuf.st_size; @@ -80,14 +80,14 @@ int FileCamera::PreCapture() { // This waits until 1 second has passed since it was modified. Effectively limiting fps to 60. // Which is kinda bogus. If we were writing to this jpg constantly faster than we are monitoring it here // we would never break out of this loop - while ( (time(nullptr) - statbuf.st_mtime) < 1 ) { - usleep(100000); + while ((time(nullptr) - statbuf.st_mtime) < 1) { + std::this_thread::sleep_for(Milliseconds(100)); } return 0; } -int FileCamera::Capture( ZMPacket &zm_packet ) { - return zm_packet.image->ReadJpeg(path, colours, subpixelorder) ? 1 : -1; +int FileCamera::Capture(std::shared_ptr &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 61d38cfe3..cc5d38acd 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -27,33 +27,31 @@ // accessed using a single file which contains the latest jpeg data // class FileCamera : public Camera { -protected: - char path[PATH_MAX]; - -public: - 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 ); } + public: + 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() override; void Initialise(); void Terminate(); - int PreCapture(); - int Capture( ZMPacket &p ); - int PostCapture(); - int Close() { return 0; }; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; + int Close() override { return 0; }; + + const std::string &Path() const { return path; } + + private: + std::string path; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_font.cpp b/src/zm_font.cpp index 6383ddd9a..7ecaaa6b1 100644 --- a/src/zm_font.cpp +++ b/src/zm_font.cpp @@ -1,77 +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 +#include -int ZmFont::ReadFontFile(const std::string &loc) { - FILE *f = fopen(loc.c_str(), "rb"); - if ( !f ) return -1; // FILE NOT FOUND +constexpr uint8 kRequiredZmFntVersion = 1; - font = new ZMFONT; +constexpr uint8 FontVariant::kMaxNumCodePoints; +constexpr uint8 FontVariant::kMaxCharHeight; +constexpr uint8 FontVariant::kMaxCharWidth; - size_t header_size = 8 + (sizeof(ZMFONT_BH) * NUM_FONT_SIZES); +FontVariant::FontVariant() : char_height_(0), char_width_(0), char_padding_(0), codepoint_count_(0) {} - // MAGIC + pad + BitmapHeaders - size_t readsize = fread(&font[0], 1, header_size, f); - if ( readsize < header_size ) { - delete font; - font = nullptr; - fclose(f); - return -2; // EOF reached, invalid file +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 ( memcmp(font->MAGIC, "ZMFNT", 5) != 0 ) { // Check whether magic is correct - delete font; - font = nullptr; - fclose(f); - return -3; + if (char_width_ > kMaxCharWidth) { + throw std::invalid_argument("char_width > kMaxCharWidth"); } - struct stat st; - stat(loc.c_str(), &st); + if (bitmap_.size() % char_height_ != 0) { + throw std::invalid_argument("bitmap has wrong length"); + } - for ( int i = 0; i < NUM_FONT_SIZES; i++ ) { - /* Character Width cannot be greater than 64 as a row is represented as a uint64_t, - height cannot be greater than 200(arbitary number which i have chosen, shouldn't need more than this) and - idx should not be more than filesize - */ - if ( (font->header[i].charWidth > 64 && font->header[i].charWidth == 0) || - (font->header[i].charHeight > 200 && font->header[i].charHeight == 0) || - (font->header[i].idx > st.st_size) ) { - delete font; - font = nullptr; - fclose(f); - return -4; + 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; } - } // end foreach font size - datasize = st.st_size - header_size; + std::vector bitmap; + bitmap.resize(bitmap_header.number_of_code_points * bitmap_header.char_height); - font->data = new uint64_t[datasize/sizeof(uint64_t)]; - readsize = fread(&font->data[0], 1, datasize, f); - if ( readsize < datasize ) { // Shouldn't happen - delete[] font->data; - font->data = nullptr; - delete font; - font = nullptr; - return -2; - } - fclose(f); - return 0; -} + std::size_t bitmap_bytes = bitmap.size() * sizeof(uint64); + font_file.read(reinterpret_cast(bitmap.data()), static_cast(bitmap_bytes)); -ZmFont::~ZmFont() { - if ( font && font->data ) { - delete[] font->data; - font->data = nullptr; + variants_[i] = + {bitmap_header.char_height, bitmap_header.char_width, bitmap_header.char_padding, std::move(bitmap)}; } - if ( font ) { - delete font; - font = nullptr; + if (font_file.fail()) { + return FontLoadError::kInvalidFile; } + + return FontLoadError::kOk; } -uint64_t *ZmFont::GetBitmapData() { - return &font->data[font->header[size].idx]; +const FontVariant &ZmFont::GetFontVariant(uint8 idx) const { + return variants_.at(idx); } diff --git a/src/zm_font.h b/src/zm_font.h index 22ecb4612..e1f03d7d1 100644 --- a/src/zm_font.h +++ b/src/zm_font.h @@ -1,40 +1,92 @@ +/* + * 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 "span.hpp" #include +#include -#define NUM_FONT_SIZES 4 +constexpr uint8 kNumFontSizes = 4; -struct ZMFONT_BH{ - uint16_t charHeight; // Height of every character - uint16_t charWidth; // Width of every character - uint32_t numberofCodePoints; // number of codepoints max 255 for now - uint32_t idx; // idx in data where data for the bitmap starts - uint32_t pad; // padding to round of the size +enum class FontLoadError { + kOk, + kFileNotFound, + kInvalidFile }; -struct ZMFONT { - char MAGIC[6]; // ZMFNT\0 - char pad[2]; - ZMFONT_BH header[NUM_FONT_SIZES]; - uint64_t *data; +#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: - ~ZmFont(); - int ReadFontFile(const std::string &loc); - ZMFONT *GetFont() { return font; } - void SetFontSize(int _size) { size = _size; } - uint64_t *GetBitmapData(); - uint16_t GetCharWidth() { return font->header[size].charWidth; } - uint16_t GetCharHeight() { return font->header[size].charHeight; } + FontLoadError LoadFontFile(const std::string &loc); + const FontVariant &GetFontVariant(uint8 idx) const; private: - int size = 0; - size_t datasize = 0; - ZMFONT *font = nullptr; + std::array variants_; }; #endif diff --git a/src/zm_frame.cpp b/src/zm_frame.cpp index bf47bdbf5..aadd20f8d 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, + SystemTimePoint p_timestamp, + Microseconds 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 6bb02f192..77c7a0398 100644 --- a/src/zm_frame.h +++ b/src/zm_frame.h @@ -22,7 +22,8 @@ #include "zm_event.h" #include "zm_time.h" -#include +#include "zm_zone.h" +#include enum FrameType { NORMAL = 0, @@ -34,24 +35,23 @@ enum FrameType { // This describes a frame record // class Frame { + public: + Frame(event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + SystemTimePoint p_timestamp, + Microseconds 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; - struct timeval timestamp; - struct DeltaTimeval delta; + event_id_t event_id; + int frame_id; + FrameType type; + SystemTimePoint timestamp; + Microseconds delta; int score; - + std::vector zone_stats; }; #endif // ZM_FRAME_H diff --git a/src/zm_group.cpp b/src/zm_group.cpp index 6b5110d76..9ed7a1093 100644 --- a/src/zm_group.cpp +++ b/src/zm_group.cpp @@ -20,6 +20,7 @@ #include "zm_group.h" #include "zm_logger.h" +#include "zm_utils.h" #include Group::Group() { @@ -30,7 +31,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++; @@ -39,27 +40,27 @@ Group::Group(MYSQL_ROW &dbrow) { /* If a zero or invalid p_id is passed, then the old default path will be assumed. */ Group::Group(unsigned int p_id) { - id = 0; + id = 0; - 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); - zmDbRow dbrow; - if ( !dbrow.fetch(sql) ) { - Error("Unable to load group for id %d: %s", p_id, mysql_error(&dbconn)); - } else { - unsigned int index = 0; - id = atoi(dbrow[index++]); - parent_id = dbrow[index] ? atoi(dbrow[index]): 0; index++; - strncpy(name, dbrow[index++], sizeof(name)-1); - Debug(1, "Loaded Group area %d '%s'", id, this->Name()); - } - } - if ( ! id ) { - Debug(1,"No id passed to Group constructor."); - strcpy(name, "Default"); - } + if (p_id) { + std::string sql = stringtf("SELECT `Id`, `ParentId`, `Name` FROM `Group` WHERE `Id`=%u", p_id); + Debug(2, "Loading Group for %u using %s", p_id, sql.c_str()); + zmDbRow dbrow; + if (!dbrow.fetch(sql)) { + Error("Unable to load group for id %u: %s", p_id, mysql_error(&dbconn)); + } else { + unsigned int index = 0; + id = atoi(dbrow[index++]); + parent_id = dbrow[index] ? atoi(dbrow[index]) : 0; + index++; + strncpy(name, dbrow[index++], sizeof(name) - 1); + Debug(1, "Loaded Group area %d '%s'", id, this->Name()); + } + } + if (!id) { + Debug(1, "No id passed to Group constructor."); + strcpy(name, "Default"); + } } Group::~Group() { diff --git a/src/zm_group.h b/src/zm_group.h index 4d1cf5d6e..f3ce1f3cf 100644 --- a/src/zm_group.h +++ b/src/zm_group.h @@ -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 704e2d374..295426254 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -25,6 +25,7 @@ #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}; @@ -105,12 +106,22 @@ 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; @@ -119,7 +130,6 @@ Image::Image() { buffer = 0; buffertype = ZM_BUFTYPE_DONTFREE; holdbuffer = 0; - text[0] = '\0'; blend = fptr_blend; } @@ -129,6 +139,7 @@ Image::Image(const char *filename) { width = 0; linesize = 0; height = 0; + padding = 0; pixels = 0; colours = 0; subpixelorder = 0; @@ -138,7 +149,6 @@ Image::Image(const char *filename) { buffertype = ZM_BUFTYPE_DONTFREE; holdbuffer = 0; ReadJpeg(filename, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); - text[0] = '\0'; update_function_pointers(); } @@ -150,21 +160,24 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint subpixelorder(p_subpixelorder), buffer(p_buffer) { - if ( !initialised ) + if (!initialised) Initialise(); pixels = width * height; linesize = p_width * p_colours; 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(); @@ -192,14 +205,16 @@ 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) { - text[0] = '\0'; width = frame->width; height = frame->height; pixels = width*height; @@ -211,14 +226,10 @@ Image::Image(const AVFrame *frame) { imagePixFormat = AV_PIX_FMT_RGBA; //(AVPixelFormat)frame->format; -#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_RGB0, width, height); -#endif + padding = 0; buffer = nullptr; holdbuffer = 0; @@ -234,16 +245,16 @@ int Image::PopulateFrame(AVFrame *frame) { width, height, linesize, colours, size, av_get_pix_fmt_name(imagePixFormat) ); - AVBufferRef *ref = av_buffer_create(buffer, size, + AVBufferRef *ref = av_buffer_create(buffer, size, dont_free, /* Free callback */ nullptr, /* opaque */ 0 /* flags */ ); - if ( !ref ) { + if (!ref) { Warning("Failed to create av_buffer"); } frame->buf[0] = ref; -#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( frame->data, frame->linesize, @@ -255,58 +266,62 @@ int Image::PopulateFrame(AVFrame *frame) { av_make_error_string(size).c_str()); return size; } -#else - 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) -void Image::Assign(const AVFrame *frame) { +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(); - PopulateFrame(dest_frame); - zm_dump_video_frame(frame, "source frame before convert"); - dest_frame->pts = frame->pts; -#if HAVE_LIBSWSCALE sws_convert_context = sws_getCachedContext( sws_convert_context, frame->width, frame->height, (AVPixelFormat)frame->format, width, height, format, - //SWS_BICUBIC, - SWS_POINT | SWS_BITEXACT, + SWS_BICUBIC, + //SWS_POINT | SWS_BITEXACT, 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 %ux%u to target format %u %ux%u", - frame->format, frame->width, frame->height, - format, width, height); -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE - zm_dump_video_frame(dest_frame, "dest frame after convert"); + 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(); + 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"); + update_function_pointers(); + 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; @@ -316,7 +331,7 @@ 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(); } @@ -327,27 +342,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; } @@ -544,11 +564,11 @@ void Image::Initialise() { g_u_table = g_u_table_global; b_u_table = b_u_table_global; - int res = font.ReadFontFile(config.font_file_location); - if ( res == -1 ) { + FontLoadError res = font.LoadFontFile(config.font_file_location); + if ( res == FontLoadError::kFileNotFound ) { Panic("Invalid font location: %s", config.font_file_location); - } else if ( res == -2 || res == -3 || res == -4 ) { - Panic("Invalid font file."); + } else if ( res == FontLoadError::kInvalidFile ) { + Panic("Invalid font file."); } initialised = true; } @@ -632,7 +652,7 @@ void Image::AssignDirect( 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", @@ -667,6 +687,7 @@ void Image::AssignDirect( subpixelorder = p_subpixelorder; pixels = width * height; size = new_buffer_size; + update_function_pointers(); } // end void Image::AssignDirect void Image::Assign( @@ -753,7 +774,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); } @@ -768,6 +789,7 @@ void Image::Assign(const Image &image) { linesize = image.linesize; } + update_function_pointers(); if ( image.buffer != buffer ) (*fptr_imgbufcpy)(buffer, image.buffer, size); } @@ -792,10 +814,10 @@ Image *Image::HighlightEdges( /* Set image to all black */ high_image->Clear(); - unsigned int lo_x = limits ? limits->Lo().X() : 0; - unsigned int lo_y = limits ? limits->Lo().Y() : 0; - unsigned int hi_x = limits ? limits->Hi().X() : width-1; - unsigned int hi_y = limits ? limits->Hi().Y() : height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( p_colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { @@ -913,125 +935,123 @@ bool Image::WriteRaw(const char *filename) const { return true; } -bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder) { +bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = readjpg_dcinfo; - if ( !cinfo ) { - cinfo = readjpg_dcinfo = new jpeg_decompress_struct; - cinfo->err = jpeg_std_error(&jpg_err.pub); + if (!readjpg_dcinfo) { + readjpg_dcinfo = new jpeg_decompress_struct; + readjpg_dcinfo->err = jpeg_std_error(&jpg_err.pub); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; - jpeg_create_decompress(cinfo); + jpeg_create_decompress(readjpg_dcinfo); } FILE *infile; - if ( (infile = fopen(filename, "rb")) == nullptr ) { - Error("Can't open %s: %s", filename, strerror(errno)); + if ((infile = fopen(filename.c_str(), "rb")) == nullptr) { + Error("Can't open %s: %s", filename.c_str(), strerror(errno)); return false; } - if ( setjmp(jpg_err.setjmp_buffer) ) { - jpeg_abort_decompress(cinfo); + if (setjmp(jpg_err.setjmp_buffer)) { + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } - jpeg_stdio_src(cinfo, infile); + jpeg_stdio_src(readjpg_dcinfo, infile); - jpeg_read_header(cinfo, TRUE); + jpeg_read_header(readjpg_dcinfo, true); - if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { + if ((readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3)) { Error("Unexpected colours when reading jpeg image: %d", colours); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { - zm_use_std_huff_tables(cinfo); + if (readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) { + zm_use_std_huff_tables(readjpg_dcinfo); } - new_width = cinfo->image_width; - new_height = cinfo->image_height; + new_width = readjpg_dcinfo->image_width; + new_height = readjpg_dcinfo->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); } - switch ( p_colours ) { + switch (p_colours) { case ZM_COLOUR_GRAY8: - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; + readjpg_dcinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + readjpg_dcinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + readjpg_dcinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + readjpg_dcinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + readjpg_dcinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif case ZM_COLOUR_RGB24: default: - new_colours = ZM_COLOUR_RGB24; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { + new_colours = ZM_COLOUR_RGB24; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + readjpg_dcinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif - */ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; + */ + readjpg_dcinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; } // end switch p_colours - if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { + if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) { Error("Failed requesting writeable buffer for reading JPEG image."); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(readjpg_dcinfo); fclose(infile); return false; } - jpeg_start_decompress(cinfo); + jpeg_start_decompress(readjpg_dcinfo); JSAMPROW row_pointer = buffer; - while ( cinfo->output_scanline < cinfo->output_height ) { - jpeg_read_scanlines(cinfo, &row_pointer, 1); + while (readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height) { + jpeg_read_scanlines(readjpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } - jpeg_finish_decompress(cinfo); - + jpeg_finish_decompress(readjpg_dcinfo); fclose(infile); return true; @@ -1040,69 +1060,74 @@ cinfo->out_color_space = JCS_RGB; // Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults. // Note quality=zero means default -bool Image::WriteJpeg(const char *filename, int quality_override) const { - return Image::WriteJpeg(filename, quality_override, (timeval){0,0}, false); +bool Image::WriteJpeg(const std::string &filename, int quality_override) const { + return Image::WriteJpeg(filename, quality_override, {}, false); } -bool Image::WriteJpeg(const char *filename) const { - return Image::WriteJpeg(filename, 0, (timeval){0,0}, false); +bool Image::WriteJpeg(const std::string &filename) const { + return Image::WriteJpeg(filename, 0, {}, false); } -bool Image::WriteJpeg(const char *filename, bool on_blocking_abort) const { - return Image::WriteJpeg(filename, 0, (timeval){0,0}, on_blocking_abort); +bool Image::WriteJpeg(const std::string &filename, bool on_blocking_abort) const { + return Image::WriteJpeg(filename, 0, {}, on_blocking_abort); } -bool Image::WriteJpeg(const char *filename, struct timeval timestamp) const { +bool Image::WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const { return Image::WriteJpeg(filename, 0, timestamp, false); } -bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp) const { +bool Image::WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const { return Image::WriteJpeg(filename, quality_override, timestamp, false); } -bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval timestamp, bool on_blocking_abort) const { - if ( config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8) ) { +bool Image::WriteJpeg(const std::string &filename, + const int &quality_override, + SystemTimePoint timestamp, + bool on_blocking_abort) const { + if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) { Image temp_image(*this); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort); } int quality = quality_override ? quality_override : config.jpeg_file_quality; - struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; + jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; FILE *outfile = nullptr; - static int raw_fd = 0; - raw_fd = 0; + int raw_fd = 0; - if ( !cinfo ) { + if (!cinfo) { cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo->err = jpeg_std_error(&jpg_err.pub); jpeg_create_compress(cinfo); } - if ( !on_blocking_abort ) { + if (!on_blocking_abort) { jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; } else { jpg_err.pub.error_exit = zm_jpeg_error_silent; jpg_err.pub.emit_message = zm_jpeg_emit_silence; - if ( setjmp(jpg_err.setjmp_buffer) ) { + if (setjmp(jpg_err.setjmp_buffer)) { jpeg_abort_compress(cinfo); - Debug(1, "Aborted a write mid-stream and %s and %d", (outfile == nullptr) ? "closing file" : "file not opened", raw_fd); - if ( raw_fd ) + Debug(1, + "Aborted a write mid-stream and %s and %d", + (outfile == nullptr) ? "closing file" : "file not opened", + raw_fd); + if (raw_fd) close(raw_fd); - if ( outfile ) + if (outfile) fclose(outfile); return false; } } - if ( !on_blocking_abort ) { - if ( (outfile = fopen(filename, "wb")) == nullptr ) { - Error("Can't open %s for writing: %s", filename, strerror(errno)); + if (!on_blocking_abort) { + if ((outfile = fopen(filename.c_str(), "wb")) == nullptr) { + Error("Can't open %s for writing: %s", filename.c_str(), strerror(errno)); return false; } } else { - raw_fd = open(filename, O_WRONLY|O_NONBLOCK|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); - if ( raw_fd < 0 ) + 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 ) { + if (outfile == nullptr) { close(raw_fd); return false; } @@ -1113,58 +1138,58 @@ bool Image::WriteJpeg(const char *filename, int quality_override, struct timeval cinfo->image_width = width; /* image width and height, in pixels */ cinfo->image_height = height; - switch ( colours ) { + switch (colours) { case ZM_COLOUR_GRAY8: - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if ( subpixelorder == ZM_SUBPIX_ORDER_RGBA ) { - cinfo->in_color_space = JCS_EXT_RGBX; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if ( subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - Warning("Unknwon subpixelorder %d", subpixelorder); - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } - break; + cinfo->input_components = 4; + if (subpixelorder == ZM_SUBPIX_ORDER_RGBA) { + cinfo->in_color_space = JCS_EXT_RGBX; + } else if (subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if (subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if (subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + Warning("Unknwon subpixelorder %d", subpixelorder); + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } + break; #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress(cinfo); + fclose(outfile); + return false; +#endif + case ZM_COLOUR_RGB24: + default: + cinfo->input_components = 3; + if (subpixelorder == ZM_SUBPIX_ORDER_BGR) { +#ifdef JCS_EXTENSIONS + cinfo->in_color_space = JCS_EXT_BGR; +#else + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); jpeg_abort_compress(cinfo); fclose(outfile); return false; #endif - case ZM_COLOUR_RGB24: - default: - cinfo->input_components = 3; - if ( subpixelorder == ZM_SUBPIX_ORDER_BGR) { -#ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; -#else - Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); - jpeg_abort_compress(cinfo); - fclose(outfile); - return false; -#endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif */ - cinfo->in_color_space = JCS_RGB; - } - break; + cinfo->in_color_space = JCS_RGB; + } + break; } // end switch(colours) jpeg_set_defaults(cinfo); @@ -1172,12 +1197,12 @@ 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. - if ( timestamp.tv_sec ) { + if (timestamp.time_since_epoch() > Seconds(0)) { #define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds #define EXIFTIMES_MS_LEN 0x03 #define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes @@ -1186,22 +1211,29 @@ 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))); - 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 + + tm timestamp_tm = {}; + time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp); + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(×tamp_t, ×tamp_tm)); + Seconds ts_sec = std::chrono::duration_cast(timestamp.time_since_epoch()); + Microseconds ts_usec = std::chrono::duration_cast(timestamp.time_since_epoch() - ts_sec); + // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it + snprintf(msbuf, sizeof msbuf, "%06d", static_cast(ts_usec.count())); + unsigned char exiftimes[82] = { - 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, - 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00 }; - memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, + 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00}; + memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf, EXIFTIMES_LEN); memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN); - jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes)); + jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *) exiftimes, sizeof(exiftimes)); } JSAMPROW row_pointer = buffer; /* pointer to a single row */ - while ( cinfo->next_scanline < cinfo->image_height ) { + while (cinfo->next_scanline < cinfo->image_height) { jpeg_write_scanlines(cinfo, &row_pointer, 1); row_pointer += linesize; } @@ -1211,121 +1243,116 @@ cinfo->out_color_space = JCS_RGB; return true; } -bool Image::DecodeJpeg( - const JOCTET *inbuffer, - int inbuffer_size, - unsigned int p_colours, - unsigned int p_subpixelorder) +bool Image::DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = decodejpg_dcinfo; - if ( !cinfo ) { - cinfo = decodejpg_dcinfo = new jpeg_decompress_struct; - cinfo->err = jpeg_std_error( &jpg_err.pub ); + if (!decodejpg_dcinfo) { + decodejpg_dcinfo = new jpeg_decompress_struct; + decodejpg_dcinfo->err = jpeg_std_error(&jpg_err.pub); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; - jpeg_create_decompress( cinfo ); + jpeg_create_decompress(decodejpg_dcinfo); } - if ( setjmp(jpg_err.setjmp_buffer) ) { - jpeg_abort_decompress(cinfo); + if (setjmp(jpg_err.setjmp_buffer)) { + jpeg_abort_decompress(decodejpg_dcinfo); return false; } - zm_jpeg_mem_src(cinfo, inbuffer, inbuffer_size); + zm_jpeg_mem_src(decodejpg_dcinfo, inbuffer, inbuffer_size); - jpeg_read_header(cinfo, TRUE); + jpeg_read_header(decodejpg_dcinfo, TRUE); - if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) { + if ((decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3)) { Error("Unexpected colours when reading jpeg image: %d", colours); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(decodejpg_dcinfo); return false; } /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ - if ( cinfo->dc_huff_tbl_ptrs[0] == nullptr ) { - zm_use_std_huff_tables(cinfo); + if (decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) { + zm_use_std_huff_tables(decodejpg_dcinfo); } - new_width = cinfo->image_width; - new_height = cinfo->image_height; + new_width = decodejpg_dcinfo->image_width; + new_height = decodejpg_dcinfo->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); + width, height, new_width, new_height); } switch (p_colours) { case ZM_COLOUR_GRAY8: - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; + decodejpg_dcinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; case ZM_COLOUR_RGB32: #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGRA ) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ARGB ) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if ( p_subpixelorder == ZM_SUBPIX_ORDER_ABGR ) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if (p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif case ZM_COLOUR_RGB24: default: - new_colours = ZM_COLOUR_RGB24; - if ( p_subpixelorder == ZM_SUBPIX_ORDER_BGR ) { + new_colours = ZM_COLOUR_RGB24; + if (p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + decodejpg_dcinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ - /* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS cinfo->out_color_space = JCS_EXT_RGB; #else cinfo->out_color_space = JCS_RGB; #endif - */ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; + */ + decodejpg_dcinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; } // end switch - if ( WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr ) { + if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) { Error("Failed requesting writeable buffer for reading JPEG image."); - jpeg_abort_decompress(cinfo); + jpeg_abort_decompress(decodejpg_dcinfo); return false; } - jpeg_start_decompress(cinfo); + jpeg_start_decompress(decodejpg_dcinfo); JSAMPROW row_pointer = buffer; /* pointer to a single row */ - while ( cinfo->output_scanline < cinfo->output_height ) { - jpeg_read_scanlines(cinfo, &row_pointer, 1); + while (decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height) { + jpeg_read_scanlines(decodejpg_dcinfo, &row_pointer, 1); row_pointer += linesize; } - jpeg_finish_decompress(cinfo); + jpeg_finish_decompress(decodejpg_dcinfo); return true; } @@ -1480,7 +1507,7 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig } bool Image::Crop(const Box &limits) { - return Crop(limits.LoX(), limits.LoY(), limits.HiX(), limits.HiY()); + return Crop(limits.Lo().x_, limits.Lo().y_, limits.Hi().x_, limits.Hi().y_); } /* Far from complete */ @@ -1492,7 +1519,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 */ @@ -1701,11 +1729,6 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) { } // end void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) void Image::Blend( const Image &image, int transparency ) { -#ifdef ZM_IMAGE_PROFILING - struct timespec start,end,diff; - unsigned long long executetime; - unsigned long milpixels; -#endif uint8_t* new_buffer; if ( !( @@ -1723,19 +1746,21 @@ void Image::Blend( const Image &image, int transparency ) { new_buffer = AllocBuffer(size); #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); + TimePoint start = std::chrono::steady_clock::now(); #endif /* Do the blending */ (*blend)(buffer, image.buffer, new_buffer, size, transparency); #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); - timespec_diff(&start,&end,&diff); + TimePoint end = std::chrono::steady_clock::now(); - executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; - milpixels = (unsigned long)((long double)size)/((((long double)executetime)/1000)); - Debug(5, "Blend: %u colours blended in %llu nanoseconds, %lu million colours/s\n",size,executetime,milpixels); + std::chrono::nanoseconds diff = std::chrono::duration_cast(end - start); + uint64 mil_pixels = static_cast(size / (static_cast(diff.count()) / 1000)); + Debug(5, "Blend: %u colours blended in %" PRIi64 " nanoseconds, % " PRIi64 " million colours/s\n", + size, + static_cast(diff.count()), + mil_pixels); #endif AssignDirect(width, height, colours, subpixelorder, new_buffer, size, ZM_BUFTYPE_ZM); @@ -1839,12 +1864,6 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres /* 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; - unsigned long milpixels; -#endif - if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to get delta of different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder); @@ -1857,7 +1876,7 @@ void Image::Delta(const Image &image, Image* targetimage) const { } #ifdef ZM_IMAGE_PROFILING - clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); + TimePoint start = std::chrono::steady_clock::now(); #endif switch ( colours ) { @@ -1894,16 +1913,18 @@ 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); + TimePoint end = std::chrono::steady_clock::now(); - executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; - milpixels = (unsigned long)((long double)pixels)/((((long double)executetime)/1000)); - Debug(5, "Delta: %u delta pixels generated in %llu nanoseconds, %lu million pixels/s",pixels,executetime,milpixels); + std::chrono::nanoseconds diff = std::chrono::duration_cast(end - start); + uint64 mil_pixels = static_cast(size / (static_cast(diff.count()) / 1000)); + Debug(5, "Delta: %u delta pixels generated in %" PRIi64 " nanoseconds, % " PRIi64 " million pixels/s", + size, + static_cast(diff.count()), + mil_pixels); #endif } -const Coord Image::centreCoord( const char *text, int size=1 ) const { +const Vector2 Image::centreCoord(const char *text, int size = 1) const { int index = 0; int line_no = 0; int text_len = strlen(text); @@ -1923,12 +1944,12 @@ const Coord Image::centreCoord( const char *text, int size=1 ) const { line_no++; } - font.SetFontSize(size-1); - uint16_t char_width = font.GetCharWidth(); - uint16_t char_height = font.GetCharHeight(); + 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); + return {x, y}; } /* RGB32 compatible: complete */ @@ -1977,169 +1998,139 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/ */ void Image::Annotate( - const char *p_text, - const Coord &coord, - const unsigned int size, + const std::string &text, + const Vector2 &coord, + const uint8 size, const Rgb fg_colour, const Rgb bg_colour) { - strncpy(text, p_text, sizeof(text)-1); + 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 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 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 == kRGBTransparent); - font.SetFontSize(size-1); - const uint16_t char_width = font.GetCharWidth(); - const uint16_t char_height = font.GetCharHeight(); - const uint64_t *font_bitmap = font.GetBitmapData(); - Debug(4, "Font size %d, char_width %d char_height %d", size, char_width, char_height); + FontVariant const &font_variant = font.GetFontVariant(size - 1); + const uint16 char_width = font_variant.GetCharWidth(); + const uint16 char_height = font_variant.GetCharHeight(); - while ( (index < text_len) && (line_len = strcspn(line, "\n")) ) { - unsigned int line_width = line_len * char_width; + 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()); + } - unsigned int lo_line_x = coord.X(); - unsigned int lo_line_y = coord.Y() + (line_no * char_height); + uint32 x0_max = width - (max_line_length * char_width); + uint32 y0_max = height - (lines.size() * char_height); - unsigned int min_line_x = 0; - // FIXME What if line_width > width? - unsigned int max_line_x = width - line_width; - unsigned int min_line_y = 0; - unsigned int max_line_y = height - char_height; + // 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); - 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; + uint32 y = y0; + for (const std::string &line : lines) { + uint32 x = x0; - unsigned int hi_line_x = lo_line_x + line_width; - unsigned int hi_line_y = lo_line_y + char_height; - - // 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 < char_height; 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++ ) { - if ( line[c] > 0xFF ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; + 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)); } - uint64_t f = font_bitmap[(line[c] * char_height) + r]; - if ( !bg_trans ) memset(temp_ptr, bg_bw_col, char_width); - while ( f != 0 ) { - uint64_t t = f & -f; - int idx = char_width - __builtin_ctzll(f) + size; - *(temp_ptr + idx) = fg_bw_col; - f ^= t; + + 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); } - temp_ptr += char_width; + 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; - 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 < char_height; 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++ ) { - if ( line[c] > 0xFF ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; - } - uint64_t f = font_bitmap[(line[c] * char_height) + r]; - if ( !bg_trans ) { - for ( int i = 0; i < char_width; i++ ) { // We need to set individual r,g,b components - unsigned char *colour_ptr = temp_ptr + (i*3); - RED_PTR_RGBA(colour_ptr) = bg_r_col; - GREEN_PTR_RGBA(colour_ptr) = bg_g_col; - BLUE_PTR_RGBA(colour_ptr) = bg_b_col; + } else if (colours == ZM_COLOUR_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((y * width) + x0) * bytesPerPixel]; + + 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 ( f != 0 ) { - uint64_t t = f & -f; - int idx = char_width - __builtin_ctzll(f) + size; - unsigned char *colour_ptr = temp_ptr + (idx*3); - RED_PTR_RGBA(colour_ptr) = fg_r_col; - GREEN_PTR_RGBA(colour_ptr) = fg_g_col; - BLUE_PTR_RGBA(colour_ptr) = fg_b_col; - f ^= t; + + 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); } - temp_ptr += char_width * colours; + 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 < char_height; 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++ ) { - if ( line[c] > 0xFF ) { - Warning("Unsupported character %c in %s", line[c], line); - continue; + 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); } - uint64_t f = font_bitmap[(line[c] * char_height) + r]; - if ( !bg_trans ) { - for ( int i = 0; i < char_width; i++ ) - *(temp_ptr + i) = 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); } - while ( f != 0 ) { - uint64_t t = f & -f; - int idx = char_width - __builtin_ctzll(f) + size; - *(temp_ptr + idx) = fg_rgb_col; - f ^= t; - } - temp_ptr += char_width; + 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 ) { +void Image::Timestamp(const char *label, SystemTimePoint when, const Vector2 &coord, int label_size) { char time_text[64]; - strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime(&when)); - if ( label ) { + tm when_tm = {}; + time_t when_t = std::chrono::system_clock::to_time_t(when); + strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when_t, &when_tm)); + if (label) { // Assume label is max 64, + ' - ' + 64 chars of time_text char text[132]; snprintf(text, sizeof(text), "%s - %s", label, time_text); - Annotate(text, coord, size); + Annotate(text, coord, label_size); } else { - Annotate(time_text, coord, size); + Annotate(time_text, coord, label_size); } } @@ -2296,10 +2287,10 @@ void Image::Fill( Rgb colour, const Box *limits ) { /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - unsigned int lo_x = limits?limits->Lo().X():0; - unsigned int lo_y = limits?limits->Lo().Y():0; - unsigned int hi_x = limits?limits->Hi().X():width-1; - unsigned int hi_y = limits?limits->Hi().Y():height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned char *p = &buffer[(y*width)+lo_x]; @@ -2341,10 +2332,10 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) { /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour, subpixelorder); - unsigned int lo_x = limits?limits->Lo().X():0; - unsigned int lo_y = limits?limits->Lo().Y():0; - unsigned int hi_x = limits?limits->Hi().X():width-1; - unsigned int hi_y = limits?limits->Hi().Y():height-1; + unsigned int lo_x = limits ? limits->Lo().x_ : 0; + unsigned int lo_y = limits ? limits->Lo().y_ : 0; + unsigned int hi_x = limits ? limits->Hi().x_ : width - 1; + unsigned int hi_y = limits ? limits->Hi().y_ : height - 1; if ( colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned char *p = &buffer[(y*width)+lo_x]; @@ -2384,17 +2375,18 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { } /* Convert the colour's RGBA subpixel order into the image's subpixel order */ - colour = rgb_convert(colour,subpixelorder); + colour = rgb_convert(colour, subpixelorder); - int n_coords = polygon.getNumCoords(); - for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { - const Coord &p1 = polygon.getCoord( i ); - const Coord &p2 = polygon.getCoord( j ); + size_t n_coords = polygon.GetVertices().size(); + for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) { + const Vector2 &p1 = polygon.GetVertices()[i]; + const Vector2 &p2 = polygon.GetVertices()[j]; - int x1 = p1.X(); - int x2 = p2.X(); - int y1 = p1.Y(); - int y2 = p2.Y(); + // The last pixel we can draw is width/height - 1. Clamp to that value. + int x1 = zm::clamp(p1.x_, 0, static_cast(width - 1)); + int x2 = zm::clamp(p2.x_, 0, static_cast(width - 1)); + int y1 = zm::clamp(p1.y_, 0, static_cast(height - 1)); + int y2 = zm::clamp(p2.y_, 0, static_cast(height - 1)); double dx = x2 - x1; double dy = y2 - y1; @@ -2459,130 +2451,107 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { } // end foreach coordinate in the polygon } -/* RGB32 compatible: complete */ +// Polygon filling is based on the Scan-line Polygon filling algorithm void Image::Fill(Rgb colour, int density, const Polygon &polygon) { - if ( !(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32 ) ) { + if (!(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32)) { Panic("Attempt to fill image with unexpected colours %d", colours); } /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour, subpixelorder); - int n_coords = polygon.getNumCoords(); - int n_global_edges = 0; - Edge global_edges[n_coords]; - for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { - const Coord &p1 = polygon.getCoord(i); - const Coord &p2 = polygon.getCoord(j); + size_t n_coords = polygon.GetVertices().size(); - int x1 = p1.X(); - int x2 = p2.X(); - int y1 = p1.Y(); - int y2 = p2.Y(); + std::vector global_edges; + global_edges.reserve(n_coords); + for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) { + const Vector2 &p1 = polygon.GetVertices()[i]; + const Vector2 &p2 = polygon.GetVertices()[j]; - //Debug( 9, "x1:%d,y1:%d x2:%d,y2:%d", x1, y1, x2, y2 ); - if ( y1 == y2 ) + // Do not add horizontal edges to the global edge table. + if (p1.y_ == p2.y_) continue; - double dx = x2 - x1; - double dy = y2 - y1; + Vector2 d = p2 - p1; + + global_edges.emplace_back(std::min(p1.y_, p2.y_), + std::max(p1.y_, p2.y_), + p1.y_ < p2.y_ ? p1.x_ : p2.x_, + d.x_ / static_cast(d.y_)); - global_edges[n_global_edges].min_y = y1= Logger::DEBUG9 ) { - for ( int i = 0; i < n_global_edges; i++ ) { - Debug(9, "%d: min_y: %d, max_y:%d, min_x:%.2f, 1/m:%.2f", - i, global_edges[i].min_y, global_edges[i].max_y, global_edges[i].min_x, global_edges[i]._1_m); - } - } -#endif + std::vector active_edges; + active_edges.reserve(global_edges.size()); - int n_active_edges = 0; - Edge active_edges[n_global_edges]; - int y = global_edges[0].min_y; - do { - for ( int i = 0; i < n_global_edges; i++ ) { - if ( global_edges[i].min_y == y ) { - Debug(9, "Moving global edge"); - active_edges[n_active_edges++] = global_edges[i]; - if ( i < (n_global_edges-1) ) { - //memcpy( &global_edges[i], &global_edges[i+1], sizeof(*global_edges)*(n_global_edges-i) ); - memmove( &global_edges[i], &global_edges[i+1], sizeof(*global_edges)*(n_global_edges-i) ); - i--; - } - n_global_edges--; + int32 scan_line = global_edges[0].min_y; + while (!global_edges.empty() || !active_edges.empty()) { + // Deactivate edges with max_y < current scan line + for (auto it = active_edges.begin(); it != active_edges.end();) { + if (scan_line >= it->max_y) { + it = active_edges.erase(it); } else { - break; + it->min_x += it->_1_m; + ++it; } } - 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++ ) { - Debug(9, "%d - %d: min_y: %d, max_y:%d, min_x:%.2f, 1/m:%.2f", - y, i, active_edges[i].min_y, active_edges[i].max_y, active_edges[i].min_x, active_edges[i]._1_m ); + + // Activate edges with min_y == current scan line + for (auto it = global_edges.begin(); it != global_edges.end();) { + if (it->min_y == scan_line) { + active_edges.emplace_back(*it); + it = global_edges.erase(it); + } else { + ++it; } } -#endif - if ( !(y%density) ) { - //Debug( 9, "%d", y ); - for ( int i = 0; i < n_active_edges; ) { - int lo_x = int(round(active_edges[i++].min_x)); - int hi_x = int(round(active_edges[i++].min_x)); - if ( colours == ZM_COLOUR_GRAY8 ) { - unsigned char *p = &buffer[(y*width)+lo_x]; - for ( int x = lo_x; x <= hi_x; x++, p++) { - if ( !(x%density) ) { - //Debug( 9, " %d", x ); + + // Not enough edges to perform the fill operation. + // Continue to next line. + if (active_edges.size() < 2) { + continue; + } + std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX); + + if (!(scan_line % density)) { + for (auto it = active_edges.begin(); it < active_edges.end() - 1; ++it) { + int32 lo_x = static_cast(it->min_x); + int32 hi_x = static_cast(std::next(it)->min_x); + if (colours == ZM_COLOUR_GRAY8) { + uint8 *p = &buffer[(scan_line * width) + lo_x]; + + for (int32 x = lo_x; x <= hi_x; x++, p++) { + if (!(x % density)) { *p = colour; } } - } else if ( colours == ZM_COLOUR_RGB24 ) { - unsigned char *p = &buffer[colours*((y*width)+lo_x)]; - for ( int x = lo_x; x <= hi_x; x++, p += 3) { - if ( !(x%density) ) { - RED_PTR_RGBA(p) = RED_VAL_RGBA(colour); - GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour); - BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour); - } - } - } else if( colours == ZM_COLOUR_RGB32 ) { - Rgb *p = (Rgb*)&buffer[((y*width)+lo_x)<<2]; - for ( int x = lo_x; x <= hi_x; x++, p++) { - if ( !(x%density) ) { - /* Fast, copies the entire pixel in a single pass */ - *p = colour; - } - } - } - } - } - y++; - for ( int i = n_active_edges-1; i >= 0; i-- ) { - if ( y >= active_edges[i].max_y ) { - // Or >= as per sheets - Debug(9, "Deleting active_edge"); - if ( i < (n_active_edges-1) ) { - //memcpy( &active_edges[i], &active_edges[i+1], sizeof(*active_edges)*(n_active_edges-i) ); - memmove( &active_edges[i], &active_edges[i+1], sizeof(*active_edges)*(n_active_edges-i) ); - } - n_active_edges--; - } else { - active_edges[i].min_x += active_edges[i]._1_m; - } - } - } while ( n_global_edges || n_active_edges ); -} + } else if (colours == ZM_COLOUR_RGB24) { + constexpr uint8 bytesPerPixel = 3; + uint8 *ptr = &buffer[((scan_line * width) + lo_x) * bytesPerPixel]; -void Image::Fill(Rgb colour, const Polygon &polygon) { - Fill(colour, 1, polygon); + for (int32 x = lo_x; x <= hi_x; x++, ptr += bytesPerPixel) { + if (!(x % density)) { + RED_PTR_RGBA(ptr) = RED_VAL_RGBA(colour); + GREEN_PTR_RGBA(ptr) = GREEN_VAL_RGBA(colour); + BLUE_PTR_RGBA(ptr) = BLUE_VAL_RGBA(colour); + } + } + } else if (colours == ZM_COLOUR_RGB32) { + constexpr uint8 bytesPerPixel = 4; + Rgb *ptr = reinterpret_cast(&buffer[((scan_line * width) + lo_x) * bytesPerPixel]); + + for (int32 x = lo_x; x <= hi_x; x++, ptr++) { + if (!(x % density)) { + *ptr = colour; + } + } + } + } + } + + scan_line++; + } } void Image::Rotate(int angle) { @@ -4724,8 +4693,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]; diff --git a/src/zm_image.h b/src/zm_image.h index d45d1e204..74e5931eb 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -20,19 +20,19 @@ #ifndef ZM_IMAGE_H #define ZM_IMAGE_H -#include "zm_coord.h" #include "zm_ffmpeg.h" #include "zm_jpeg.h" #include "zm_logger.h" #include "zm_mem_utils.h" #include "zm_rgb.h" +#include "zm_time.h" +#include "zm_vector2.h" #if HAVE_ZLIB_H #include #endif // HAVE_ZLIB_H class Box; -class Image; class Polygon; #define ZM_BUFTYPE_DONTFREE 0 @@ -95,219 +95,235 @@ class Image { void update_function_pointers(); -protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; + protected: + inline void AllocImgBuffer(size_t p_bufsize) { + if ( buffer ) + DumpImgBuffer(); - static bool CompareYX(const Edge &e1, const Edge &e2) { - if ( e1.min_y == e2.min_y ) - return e1.min_x < e2.min_x; - else - 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); - buffertype = ZM_BUFTYPE_DONTFREE; - 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 }; - -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; - - 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 */ - char text[1024]; - -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); - - ~Image(); - static void Initialise(); - static void Deinitialise(); - - 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; } - - 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; + buffer = AllocBuffer(p_bufsize); + buffertype = ZM_BUFTYPE_ZM; + allocation = p_bufsize; } - } - - /* 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; }; - - inline int IsBufferHeld() const { return holdbuffer; } - inline void HoldBuffer(int tohold) { holdbuffer = tohold; } - - 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); - void Assign(const AVFrame *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); + public: + enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; + enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; - int PopulateFrame(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; - 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; - } + 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_; - bool ReadRaw(const char *filename); - bool WriteRaw(const char *filename) const; + 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); - bool ReadJpeg(const char *filename, unsigned int p_colours, unsigned int p_subpixelorder); + ~Image(); + static void Initialise(); + static void Deinitialise(); - 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; + 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; } + + 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; + } + } + + inline uint8_t* Buffer() { return buffer; } + inline const uint8_t* Buffer() const { return buffer; } + inline uint8_t* Buffer(unsigned int x, unsigned int y=0) { return &buffer[(y*linesize) + x*colours]; } + inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize) + x*colours]; } + + /* 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; }; + + inline int IsBufferHeld() const { return holdbuffer; } + inline void HoldBuffer(int tohold) { holdbuffer = tohold; } + + 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 std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder); + + bool WriteJpeg(const std::string &filename) const; + bool WriteJpeg(const std::string &filename, bool on_blocking_abort) const; + bool WriteJpeg(const std::string &filename, int quality_override) const; + bool WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const; + bool WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const; + bool WriteJpeg(const std::string &filename, + const int &quality_override, + SystemTimePoint 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[], Rgb threshold = kRGBBlack, Rgb ref_colour = kRGBRed); - //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 int size) const; - void MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour=0x00222222 ); - void Annotate(const char *p_text, const Coord &coord, unsigned int 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(); + const Vector2 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 Vector2 &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, SystemTimePoint when, const Vector2 &coord, int label_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) { Fill(colour, 1, 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); }; +// Scan-line polygon fill algorithm +namespace PolygonFill { +class Edge { + public: + Edge() = default; + Edge(int32 min_y, int32 max_y, double min_x, double _1_m) : min_y(min_y), max_y(max_y), min_x(min_x), _1_m(_1_m) {} + + 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; + } + + public: + int32 min_y; + int32 max_y; + double min_x; + double _1_m; +}; +} + #endif // ZM_IMAGE_H /* Blend functions */ diff --git a/src/zm_jpeg.cpp b/src/zm_jpeg.cpp index c3fddec1f..a71bef48e 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -37,13 +37,13 @@ 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; + zm_error_ptr zmerr = (zm_error_ptr) cinfo->err; - (zmerr->pub.format_message)(cinfo, buffer); + char buffer[JMSG_LENGTH_MAX]; + zmerr->pub.format_message(cinfo, buffer); Error("%s", buffer); - if ( ++jpeg_err_count == MAX_JPEG_ERRS ) { + if (++jpeg_err_count == MAX_JPEG_ERRS) { Fatal("Maximum number (%d) of JPEG errors reached, exiting", jpeg_err_count); } @@ -51,25 +51,25 @@ void zm_jpeg_error_exit(j_common_ptr cinfo) { } 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; + 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:") ) + 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 { /* 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); + if (zmerr->pub.trace_level >= msg_level) { + zmerr->pub.format_message(cinfo, buffer); Debug(msg_level, "%s", buffer); } } diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index fd4d077b8..beda38498 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -94,7 +94,11 @@ 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(); } } @@ -165,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; @@ -206,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" ) @@ -220,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()); } } @@ -255,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); @@ -269,17 +276,21 @@ int LibvlcCamera::PreCapture() { } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::Capture( ZMPacket &zm_packet ) { +int LibvlcCamera::Capture(std::shared_ptr &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(); - zm_packet.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; @@ -308,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 074989d17..c516a63e0 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -21,7 +21,8 @@ #define ZM_LIBVLC_CAMERA_H #include "zm_camera.h" -#include "zm_thread.h" +#include +#include #if HAVE_LIBVLC @@ -35,8 +36,11 @@ struct LibvlcPrivateData { 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 { @@ -66,11 +70,11 @@ public: void Initialise(); void Terminate(); - int PrimeCapture(); - int PreCapture(); - int Capture( ZMPacket &p ); - int PostCapture(); - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &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 c00a37e30..6fb414686 100644 --- a/src/zm_libvnc_camera.cpp +++ b/src/zm_libvnc_camera.cpp @@ -44,18 +44,20 @@ static void GotFrameBufferUpdateCallback(rfbClient *rfb, int x, int y, int w, in } static char* GetPasswordCallback(rfbClient* cl) { - Debug(1, "Getcredentials: %s", (*rfbClientGetClientData_f)(cl, &TAG_1)); + 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); + Debug(1, "credentialType != rfbCredentialTypeUser"); return nullptr; } + rfbCredential *c = (rfbCredential *)malloc(sizeof(rfbCredential)); - Debug(1, "Getcredentials: %s:%s", (*rfbClientGetClientData_f)(cl, &TAG_1), (*rfbClientGetClientData_f)(cl, &TAG_2)); + 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; @@ -106,6 +108,7 @@ VncCamera::VncCamera( p_record_audio ), mRfb(nullptr), + mVncData({}), mHost(host), mPort(port), mUser(user), @@ -144,6 +147,10 @@ VncCamera::~VncCamera() { } 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 (!mRfb) { @@ -164,13 +171,19 @@ int VncCamera::PrimeCapture() { mRfb->GetCredential = GetCredentialsCallback; mRfb->programName = "Zoneminder VNC Monitor"; - if ( mRfb->serverHost ) free(mRfb->serverHost); + 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 */ - Warning("Failed to Prime capture from %s", mHost.c_str()); mRfb = nullptr; return -1; } @@ -178,7 +191,7 @@ int VncCamera::PrimeCapture() { Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)", width, height, mRfb->width, mRfb->height); } - get_VideoStream(); + getVideoStream(); return 1; } @@ -195,20 +208,21 @@ int VncCamera::PreCapture() { return res == TRUE ? 1 : -1; } -int VncCamera::Capture(ZMPacket &zm_packet) { +int VncCamera::Capture(std::shared_ptr &zm_packet) { if (!mVncData.buffer) { Debug(1, "No buffer"); return 0; } - if (!zm_packet.image) { + 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->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->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); + 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, diff --git a/src/zm_libvnc_camera.h b/src/zm_libvnc_camera.h index c1f847991..59c531e83 100644 --- a/src/zm_libvnc_camera.h +++ b/src/zm_libvnc_camera.h @@ -10,11 +10,12 @@ // 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 +// TODO: Remove this once CentOS 7 support is dropped #ifdef max #undef max #endif + // Used by vnc callbacks struct VncPrivateData { uint8_t *buffer; @@ -51,11 +52,11 @@ public: ~VncCamera(); - int PreCapture(); - int PrimeCapture(); - int Capture(ZMPacket &packet); - int PostCapture(); - int Close(); + int PreCapture() override; + int PrimeCapture() override; + int Capture(std::shared_ptr &packet) override; + int PostCapture() override; + int Close() override; }; #endif // HAVE_LIBVNC diff --git a/src/zm_line.h b/src/zm_line.h new file mode 100644 index 000000000..ec09fc48e --- /dev/null +++ b/src/zm_line.h @@ -0,0 +1,64 @@ +/* + * 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 ZONEMINDER_SRC_ZM_LINE_H_ +#define ZONEMINDER_SRC_ZM_LINE_H_ + +#include "zm_vector2.h" + +// Represents a part of a line bounded by two end points +class LineSegment { + public: + LineSegment(Vector2 start, Vector2 end) : start_(start), end_(end) {} + + public: + Vector2 start_; + Vector2 end_; +}; + +// Represents an infinite line +class Line { + public: + Line(Vector2 p1, Vector2 p2) : position_(p1), direction_(p2 - p1) {} + explicit Line(LineSegment segment) : Line(segment.start_, segment.end_) {}; + + bool IsPointLeftOfOrColinear(Vector2 p) const { + int32 det = direction_.Determinant(p - position_); + + return det >= 0; + } + + Vector2 Intersection(Line const &line) const { + int32 det = direction_.Determinant(line.direction_); + + if (det == 0) { + // lines are parallel or overlap, no intersection + return Vector2::Inf(); + } + + Vector2 c = line.position_ - position_; + double t = c.Determinant(line.direction_) / static_cast(det); + + return position_ + direction_ * t; + } + + private: + Vector2 position_; + Vector2 direction_; +}; + +#endif //ZONEMINDER_SRC_ZM_LINE_H_ diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index f515539a1..68465fa21 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -24,8 +24,9 @@ #include #include #include +#include -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 /* Workaround for GNU/kFreeBSD and FreeBSD */ #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) @@ -45,207 +46,135 @@ static int vidioctl(int fd, int request, void *arg) { return result; } -#if HAVE_LIBSWSCALE static _AVPIXELFORMAT getFfPixFormatFromV4lPalette(int v4l_version, int palette) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - switch ( palette ) { + switch (palette) { #if defined(V4L2_PIX_FMT_RGB444) && defined(AV_PIX_FMT_RGB444) - case V4L2_PIX_FMT_RGB444 : - pixFormat = AV_PIX_FMT_RGB444; - break; + case V4L2_PIX_FMT_RGB444 : + pixFormat = AV_PIX_FMT_RGB444; + break; #endif // V4L2_PIX_FMT_RGB444 - case V4L2_PIX_FMT_RGB555 : - pixFormat = AV_PIX_FMT_RGB555; - break; - case V4L2_PIX_FMT_RGB565 : - pixFormat = AV_PIX_FMT_RGB565; - break; - case V4L2_PIX_FMT_BGR24 : - pixFormat = AV_PIX_FMT_BGR24; - break; - case V4L2_PIX_FMT_RGB24 : - pixFormat = AV_PIX_FMT_RGB24; - break; - case V4L2_PIX_FMT_BGR32 : - pixFormat = AV_PIX_FMT_BGRA; - break; - case V4L2_PIX_FMT_RGB32 : - pixFormat = AV_PIX_FMT_ARGB; - break; - case V4L2_PIX_FMT_GREY : - pixFormat = AV_PIX_FMT_GRAY8; - break; - case V4L2_PIX_FMT_YUYV : - pixFormat = AV_PIX_FMT_YUYV422; - break; - case V4L2_PIX_FMT_YUV422P : - pixFormat = AV_PIX_FMT_YUV422P; - break; - case V4L2_PIX_FMT_YUV411P : - pixFormat = AV_PIX_FMT_YUV411P; - break; + case V4L2_PIX_FMT_RGB555 : + pixFormat = AV_PIX_FMT_RGB555; + break; + case V4L2_PIX_FMT_RGB565 : + pixFormat = AV_PIX_FMT_RGB565; + break; + case V4L2_PIX_FMT_BGR24 : + pixFormat = AV_PIX_FMT_BGR24; + break; + case V4L2_PIX_FMT_RGB24 : + pixFormat = AV_PIX_FMT_RGB24; + break; + case V4L2_PIX_FMT_BGR32 : + pixFormat = AV_PIX_FMT_BGRA; + break; + case V4L2_PIX_FMT_RGB32 : + pixFormat = AV_PIX_FMT_ARGB; + break; + case V4L2_PIX_FMT_GREY : + pixFormat = AV_PIX_FMT_GRAY8; + break; + case V4L2_PIX_FMT_YUYV : + pixFormat = AV_PIX_FMT_YUYV422; + break; + case V4L2_PIX_FMT_YUV422P : + pixFormat = AV_PIX_FMT_YUV422P; + break; + case V4L2_PIX_FMT_YUV411P : + pixFormat = AV_PIX_FMT_YUV411P; + break; #ifdef V4L2_PIX_FMT_YUV444 - case V4L2_PIX_FMT_YUV444 : - pixFormat = AV_PIX_FMT_YUV444P; - break; + case V4L2_PIX_FMT_YUV444 : + pixFormat = AV_PIX_FMT_YUV444P; + break; #endif // V4L2_PIX_FMT_YUV444 - case V4L2_PIX_FMT_YUV410 : - pixFormat = AV_PIX_FMT_YUV410P; + case V4L2_PIX_FMT_YUV410 : + pixFormat = AV_PIX_FMT_YUV410P; + break; + case V4L2_PIX_FMT_YUV420 : + pixFormat = AV_PIX_FMT_YUV420P; + break; + case V4L2_PIX_FMT_JPEG : + case V4L2_PIX_FMT_MJPEG : + pixFormat = AV_PIX_FMT_YUVJ444P; + break; + case V4L2_PIX_FMT_UYVY : + pixFormat = AV_PIX_FMT_UYVY422; + break; + // These don't seem to have ffmpeg equivalents + // See if you can match any of the ones in the default clause below!? + case V4L2_PIX_FMT_RGB332 : + case V4L2_PIX_FMT_RGB555X : + case V4L2_PIX_FMT_RGB565X : + //case V4L2_PIX_FMT_Y16 : + //case V4L2_PIX_FMT_PAL8 : + case V4L2_PIX_FMT_YVU410 : + case V4L2_PIX_FMT_YVU420 : + case V4L2_PIX_FMT_Y41P : + //case V4L2_PIX_FMT_YUV555 : + //case V4L2_PIX_FMT_YUV565 : + //case V4L2_PIX_FMT_YUV32 : + case V4L2_PIX_FMT_NV12 : + case V4L2_PIX_FMT_NV21 : + case V4L2_PIX_FMT_YYUV : + case V4L2_PIX_FMT_HI240 : + case V4L2_PIX_FMT_HM12 : + //case V4L2_PIX_FMT_SBGGR8 : + //case V4L2_PIX_FMT_SGBRG8 : + //case V4L2_PIX_FMT_SBGGR16 : + case V4L2_PIX_FMT_DV : + case V4L2_PIX_FMT_MPEG : + case V4L2_PIX_FMT_WNVA : + case V4L2_PIX_FMT_SN9C10X : + case V4L2_PIX_FMT_PWC1 : + case V4L2_PIX_FMT_PWC2 : + case V4L2_PIX_FMT_ET61X251 : + //case V4L2_PIX_FMT_SPCA501 : + //case V4L2_PIX_FMT_SPCA505 : + //case V4L2_PIX_FMT_SPCA508 : + //case V4L2_PIX_FMT_SPCA561 : + //case V4L2_PIX_FMT_PAC207 : + //case V4L2_PIX_FMT_PJPG : + //case V4L2_PIX_FMT_YVYU : + default : + { + Fatal("Can't find swscale format for palette %d", palette); break; - case V4L2_PIX_FMT_YUV420 : - pixFormat = AV_PIX_FMT_YUV420P; - break; - case V4L2_PIX_FMT_JPEG : - case V4L2_PIX_FMT_MJPEG : - pixFormat = AV_PIX_FMT_YUVJ444P; - break; - case V4L2_PIX_FMT_UYVY : +#if 0 + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; pixFormat = AV_PIX_FMT_UYVY422; - break; - // These don't seem to have ffmpeg equivalents - // See if you can match any of the ones in the default clause below!? - case V4L2_PIX_FMT_RGB332 : - case V4L2_PIX_FMT_RGB555X : - case V4L2_PIX_FMT_RGB565X : - //case V4L2_PIX_FMT_Y16 : - //case V4L2_PIX_FMT_PAL8 : - case V4L2_PIX_FMT_YVU410 : - case V4L2_PIX_FMT_YVU420 : - case V4L2_PIX_FMT_Y41P : - //case V4L2_PIX_FMT_YUV555 : - //case V4L2_PIX_FMT_YUV565 : - //case V4L2_PIX_FMT_YUV32 : - case V4L2_PIX_FMT_NV12 : - case V4L2_PIX_FMT_NV21 : - case V4L2_PIX_FMT_YYUV : - case V4L2_PIX_FMT_HI240 : - case V4L2_PIX_FMT_HM12 : - //case V4L2_PIX_FMT_SBGGR8 : - //case V4L2_PIX_FMT_SGBRG8 : - //case V4L2_PIX_FMT_SBGGR16 : - case V4L2_PIX_FMT_DV : - case V4L2_PIX_FMT_MPEG : - case V4L2_PIX_FMT_WNVA : - case V4L2_PIX_FMT_SN9C10X : - case V4L2_PIX_FMT_PWC1 : - case V4L2_PIX_FMT_PWC2 : - case V4L2_PIX_FMT_ET61X251 : - //case V4L2_PIX_FMT_SPCA501 : - //case V4L2_PIX_FMT_SPCA505 : - //case V4L2_PIX_FMT_SPCA508 : - //case V4L2_PIX_FMT_SPCA561 : - //case V4L2_PIX_FMT_PAC207 : - //case V4L2_PIX_FMT_PJPG : - //case V4L2_PIX_FMT_YVYU : - default : - { - Fatal("Can't find swscale format for palette %d", palette); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } - } // end switch palette - } // end if v4l2 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - switch( palette ) { - case VIDEO_PALETTE_RGB32 : - if ( BigEndian ) - pixFormat = AV_PIX_FMT_ARGB; - else - pixFormat = AV_PIX_FMT_BGRA; - break; - case VIDEO_PALETTE_RGB24 : - if ( BigEndian ) - pixFormat = AV_PIX_FMT_RGB24; - else - pixFormat = AV_PIX_FMT_BGR24; - break; - case VIDEO_PALETTE_GREY : - pixFormat = AV_PIX_FMT_GRAY8; - break; - case VIDEO_PALETTE_RGB555 : - pixFormat = AV_PIX_FMT_RGB555; - break; - case VIDEO_PALETTE_RGB565 : - pixFormat = AV_PIX_FMT_RGB565; - break; - case VIDEO_PALETTE_YUYV : - case VIDEO_PALETTE_YUV422 : - pixFormat = AV_PIX_FMT_YUYV422; - break; - case VIDEO_PALETTE_YUV422P : - pixFormat = AV_PIX_FMT_YUV422P; - break; - case VIDEO_PALETTE_YUV420P : - pixFormat = AV_PIX_FMT_YUV420P; - break; - default : - { - Fatal("Can't find swscale format for palette %d", palette); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_YUVJ444P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } - } // end switch palette - } // end if v4l1 -#endif // ZM_HAS_V4L1 + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; +#endif + } + } // end switch palette + return pixFormat; } // end getFfPixFormatFromV4lPalette -#endif // HAVE_LIBSWSCALE -#if ZM_HAS_V4L2 static char palette_desc[32]; /* Automatic format selection preferred formats */ static const uint32_t prefered_rgb32_formats[] = { @@ -279,7 +208,6 @@ static const uint32_t prefered_gray8_formats[] = { V4L2_PIX_FMT_YUV422P, V4L2_PIX_FMT_YUV420 }; -#endif int LocalCamera::camera_count = 0; int LocalCamera::channel_count = 0; @@ -289,16 +217,9 @@ int LocalCamera::standards[VIDEO_MAX_FRAME]; int LocalCamera::vid_fd = -1; int LocalCamera::v4l_version = 0; -#if ZM_HAS_V4L2 LocalCamera::V4L2Data LocalCamera::v4l2_data; -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 -LocalCamera::V4L1Data LocalCamera::v4l1_data; -#endif // ZM_HAS_V4L1 -#if HAVE_LIBSWSCALE AVFrame **LocalCamera::capturePictures = nullptr; -#endif // HAVE_LIBSWSCALE LocalCamera *LocalCamera::last_camera = nullptr; @@ -336,12 +257,12 @@ LocalCamera::LocalCamera( v4l_multi_buffer = p_v4l_multi_buffer; v4l_captures_per_frame = p_v4l_captures_per_frame; - if ( capture ) { - if ( device_prime ) { + if (capture) { + if (device_prime) { 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++; @@ -355,10 +276,10 @@ LocalCamera::LocalCamera( /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ uint32_t checkval = 0xAABBCCDD; - if ( *(unsigned char*)&checkval == 0xDD ) { + if (*(unsigned char*)&checkval == 0xDD) { BigEndian = 0; Debug(2, "little-endian processor detected"); - } else if ( *(unsigned char*)&checkval == 0xAA ) { + } else if (*(unsigned char*)&checkval == 0xAA) { BigEndian = 1; Debug(2, "Big-endian processor detected"); } else { @@ -366,28 +287,27 @@ LocalCamera::LocalCamera( BigEndian = 0; } -#if ZM_HAS_V4L2 - if ( v4l_version == 2 && palette == 0 ) { + if (palette == 0) { /* Use automatic format selection */ Debug(2,"Using automatic format selection"); palette = AutoSelectFormat(colours); - if ( palette == 0 ) { + if (palette == 0) { Error("Automatic format selection failed. Falling back to YUYV"); palette = V4L2_PIX_FMT_YUYV; } else { - if ( capture ) { + if (capture) { Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", - palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); + palette_desc, + static_cast((palette >> 24) & 0xff), + static_cast((palette >> 16) & 0xff), + static_cast((palette >> 8) & 0xff), + static_cast((palette) & 0xff)); } } } -#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 (standard != last_camera->standard) Warning("Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong"); @@ -398,270 +318,133 @@ LocalCamera::LocalCamera( Warning("Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong"); } -#if HAVE_LIBSWSCALE /* Get ffmpeg pixel format based on capture palette and endianness */ - capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); + capturePixFormat = getFfPixFormatFromV4lPalette(v4l_version, palette); imagePixFormat = AV_PIX_FMT_NONE; -#endif // HAVE_LIBSWSCALE } - /* V4L2 format matching */ -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - /* Try to find a match for the selected palette and target colourspace */ + /* Try to find a match for the selected palette and target colourspace */ - /* RGB32 palette and 32bit target colourspace */ - if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_ARGB; + /* RGB32 palette and 32bit target colourspace */ + if (palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_ARGB; - /* BGR32 palette and 32bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_BGRA; + /* BGR32 palette and 32bit target colourspace */ + } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_BGRA; - /* RGB24 palette and 24bit target colourspace */ - } else if ( palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24 ) { - conversion_type = 0; - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_BGR; + /* RGB24 palette and 24bit target colourspace */ + } else if (palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) { + conversion_type = 0; + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_BGR; - /* Grayscale palette and grayscale target colourspace */ - } else if ( palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ - } else { - if ( capture ) { -#if HAVE_LIBSWSCALE - Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", - (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); -#else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); -#endif - } -#if HAVE_LIBSWSCALE - /* Try using swscale for the conversion */ - conversion_type = 1; - Debug(2, "Using swscale for image conversion"); - if ( colours == ZM_COLOUR_RGB32 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - imagePixFormat = AV_PIX_FMT_RGBA; - } else if ( colours == ZM_COLOUR_RGB24 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - imagePixFormat = AV_PIX_FMT_RGB24; - } else if ( colours == ZM_COLOUR_GRAY8 ) { - subpixelorder = ZM_SUBPIX_ORDER_NONE; - imagePixFormat = AV_PIX_FMT_GRAY8; - } else { - Panic("Unexpected colours: %u",colours); - } - if ( capture ) { -#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) - if ( !sws_isSupportedInput(capturePixFormat) ) { - Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx", - (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff)); - conversion_type = 2; /* Try ZM format conversions */ - } - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx", - (imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff)); - conversion_type = 2; /* Try ZM format conversions */ - } -#endif - } -#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 == V4L2_PIX_FMT_YUYV ) { - conversion_type = 2; - } - - /* JPEG */ - if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { - Debug(2,"Using JPEG image decoding"); - conversion_type = 3; - } - - if ( conversion_type == 2 ) { - Debug(2,"Using ZM for image conversion"); - if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) { - conversion_fptr = &std_convert_argb_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8 ) { - conversion_fptr = &std_convert_bgra_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8 ) { - /* Fast YUYV->Grayscale conversion by extracting the Y channel */ - if ( config.cpu_extensions && sse_version >= 35 ) { - conversion_fptr = &ssse3_convert_yuyv_gray8; - Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); - } else { - conversion_fptr = &std_convert_yuyv_gray8; - Debug(2,"Using standard YUYV->grayscale fast conversion"); - } - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_yuyv_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_yuyv_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb555_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb555_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb565_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb565_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else { - Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); - } - } // end if conversion_type == 2 - } // end if needs conversion - } // end if v4l2 -#endif // ZM_HAS_V4L2 - - /* V4L1 format matching */ -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - /* Try to find a match for the selected palette and target colourspace */ - - /* RGB32 palette and 32bit target colourspace */ - if ( palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_RGB32 ) { - conversion_type = 0; - if ( BigEndian ) { - subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else { - subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } - - /* RGB24 palette and 24bit target colourspace */ - } else if ( palette == VIDEO_PALETTE_RGB24 && colours == ZM_COLOUR_RGB24 ) { - conversion_type = 0; - if ( BigEndian ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else { - subpixelorder = ZM_SUBPIX_ORDER_BGR; - } - - /* Grayscale palette and grayscale target colourspace */ - } else if ( palette == VIDEO_PALETTE_GREY && colours == ZM_COLOUR_GRAY8 ) { - conversion_type = 0; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ - } else { - if ( capture ) - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); -#if HAVE_LIBSWSCALE - /* Try using swscale for the conversion */ - conversion_type = 1; - Debug(2,"Using swscale for image conversion"); - if ( colours == ZM_COLOUR_RGB32 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - imagePixFormat = AV_PIX_FMT_RGBA; - } else if ( colours == ZM_COLOUR_RGB24 ) { - subpixelorder = ZM_SUBPIX_ORDER_RGB; - imagePixFormat = AV_PIX_FMT_RGB24; - } else if ( colours == ZM_COLOUR_GRAY8 ) { - subpixelorder = ZM_SUBPIX_ORDER_NONE; - imagePixFormat = AV_PIX_FMT_GRAY8; - } else { - Panic("Unexpected colours: %u", colours); - } - if ( capture ) { - if ( !sws_isSupportedInput(capturePixFormat) ) { - Error("swscale does not support the used capture format"); - conversion_type = 2; /* Try ZM format conversions */ - } - if ( !sws_isSupportedOutput(imagePixFormat) ) { - Error("swscale does not support the target format"); - conversion_type = 2; /* Try ZM format conversions */ - } - } - - /* 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 ) { - if ( BigEndian ) { - conversion_fptr = &std_convert_argb_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else { - conversion_fptr = &std_convert_bgra_gray8; - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_GRAY8 ) { - /* Fast YUYV->Grayscale conversion by extracting the Y channel */ - if ( config.cpu_extensions && sse_version >= 35 ) { - conversion_fptr = &ssse3_convert_yuyv_gray8; - Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); - } else { - conversion_fptr = &std_convert_yuyv_gray8; - Debug(2,"Using standard YUYV->grayscale fast conversion"); - } - subpixelorder = ZM_SUBPIX_ORDER_NONE; - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_yuyv_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422) && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_yuyv_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb555_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB555 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb555_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB24 ) { - conversion_fptr = &zm_convert_rgb565_rgb; - subpixelorder = ZM_SUBPIX_ORDER_RGB; - } else if ( palette == VIDEO_PALETTE_RGB565 && colours == ZM_COLOUR_RGB32 ) { - conversion_fptr = &zm_convert_rgb565_rgba; - subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } else { - Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); - } - } // end if conversion_type == 2 + /* Grayscale palette and grayscale target colourspace */ + } else if (palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) { + conversion_type = 0; + subpixelorder = ZM_SUBPIX_ORDER_NONE; + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + } else { + if (capture) { + Info( + "No direct match for the selected palette (%d) and target colorspace (%02u). Format conversion is required, performance penalty expected", + capturePixFormat, + colours); } - } -#endif // ZM_HAS_V4L1 + /* Try using swscale for the conversion */ + conversion_type = 1; + Debug(2, "Using swscale for image conversion"); + if (colours == ZM_COLOUR_RGB32) { + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + imagePixFormat = AV_PIX_FMT_RGBA; + } else if (colours == ZM_COLOUR_RGB24) { + subpixelorder = ZM_SUBPIX_ORDER_RGB; + imagePixFormat = AV_PIX_FMT_RGB24; + } else if (colours == ZM_COLOUR_GRAY8) { + subpixelorder = ZM_SUBPIX_ORDER_NONE; + imagePixFormat = AV_PIX_FMT_GRAY8; + } else { + Panic("Unexpected colours: %u",colours); + } + if (capture) { + if (!sws_isSupportedInput(capturePixFormat)) { + Error("swscale does not support the used capture format: %d", capturePixFormat); + conversion_type = 2; /* Try ZM format conversions */ + } + if (!sws_isSupportedOutput(imagePixFormat)) { + Error("swscale does not support the target format: 0x%d", imagePixFormat); + conversion_type = 2; /* Try ZM format conversions */ + } + } + /* Our YUYV->Grayscale conversion is a lot faster than swscale's */ + if (colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) { + conversion_type = 2; + } + + /* JPEG */ + if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + Debug(2,"Using JPEG image decoding"); + conversion_type = 3; + } + + if (conversion_type == 2) { + Debug(2,"Using ZM for image conversion"); + if ( palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8 ) { + conversion_fptr = &std_convert_argb_gray8; + subpixelorder = ZM_SUBPIX_ORDER_NONE; + } else if (palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_GRAY8) { + conversion_fptr = &std_convert_bgra_gray8; + subpixelorder = ZM_SUBPIX_ORDER_NONE; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_GRAY8) { + /* Fast YUYV->Grayscale conversion by extracting the Y channel */ + if (config.cpu_extensions && sse_version >= 35) { + conversion_fptr = &ssse3_convert_yuyv_gray8; + Debug(2,"Using SSSE3 YUYV->grayscale fast conversion"); + } else { + conversion_fptr = &std_convert_yuyv_gray8; + Debug(2,"Using standard YUYV->grayscale fast conversion"); + } + subpixelorder = ZM_SUBPIX_ORDER_NONE; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_yuyv_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_YUYV && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_yuyv_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_rgb555_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_RGB555 && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_rgb555_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB24) { + conversion_fptr = &zm_convert_rgb565_rgb; + subpixelorder = ZM_SUBPIX_ORDER_RGB; + } else if (palette == V4L2_PIX_FMT_RGB565 && colours == ZM_COLOUR_RGB32) { + conversion_fptr = &zm_convert_rgb565_rgba; + subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } else { + Fatal("Unable to find a suitable format conversion for the selected palette and target colorspace."); + } + } // end if conversion_type == 2 + } // end if needs conversion last_camera = this; Debug(3, "Selected subpixelorder: %u", subpixelorder); -#if HAVE_LIBSWSCALE /* Initialize swscale stuff */ - if ( capture and (conversion_type == 1) ) { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + if (capture and (conversion_type == 1)) { tmpPicture = av_frame_alloc(); -#else - tmpPicture = avcodec_alloc_frame(); -#endif - if ( !tmpPicture ) + + if (!tmpPicture) Fatal("Could not allocate temporary picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) unsigned int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); -#else - unsigned int pSize = avpicture_get_size(imagePixFormat, width, height); -#endif - if ( pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %u", pSize, imagesize); + + if (pSize != imagesize) { + Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } imgConversionContext = sws_getContext( @@ -669,465 +452,282 @@ LocalCamera::LocalCamera( width, height, imagePixFormat, SWS_BICUBIC, nullptr, nullptr, nullptr); - if ( !imgConversionContext ) { + if (!imgConversionContext) { Fatal("Unable to initialise image scaling context"); } } else { tmpPicture = nullptr; imgConversionContext = nullptr; } // end if capture and conversion_tye == swscale -#endif - if ( capture and device_prime ) + if (capture and device_prime) Initialise(); } // end LocalCamera::LocalCamera LocalCamera::~LocalCamera() { - if ( device_prime && capture ) + if (device_prime && capture) Terminate(); -#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 +int LocalCamera::Close() { + if (device_prime && capture) + Terminate(); + return 0; +}; + void LocalCamera::Initialise() { 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 ) + if ((vid_fd = open(device.c_str(), O_RDWR, 0)) < 0) Fatal("Failed to open video device %s: %s", device.c_str(), strerror(errno)); struct stat st; - if ( stat(device.c_str(), &st) < 0 ) + if (stat(device.c_str(), &st) < 0) Fatal("Failed to stat video device %s: %s", device.c_str(), strerror(errno)); - if ( !S_ISCHR(st.st_mode) ) + if (!S_ISCHR(st.st_mode)) Fatal("File %s is not device file: %s", device.c_str(), strerror(errno)); -#if ZM_HAS_V4L2 - Debug(2, "V4L2 support enabled, using V4L%d api", v4l_version); - if ( v4l_version == 2 ) { - struct v4l2_capability vid_cap; + struct v4l2_capability vid_cap; - Debug(3, "Checking video device capabilities"); - if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) - Fatal("Failed to query video device: %s", strerror(errno)); + Debug(3, "Checking video device capabilities"); + if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) + Fatal("Failed to query video device: %s", strerror(errno)); - if ( !(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) - Fatal("Video device is not video capture device"); + if ( !(vid_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) + Fatal("Video device is not video capture device"); - if ( !(vid_cap.capabilities & V4L2_CAP_STREAMING) ) - Fatal("Video device does not support streaming i/o"); + if ( !(vid_cap.capabilities & V4L2_CAP_STREAMING) ) + Fatal("Video device does not support streaming i/o"); - Debug(3, "Setting up video format"); + Debug(3, "Setting up video format"); - memset(&v4l2_data.fmt, 0, sizeof(v4l2_data.fmt)); - v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + memset(&v4l2_data.fmt, 0, sizeof(v4l2_data.fmt)); + v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_G_FMT, &v4l2_data.fmt ) < 0 ) - Fatal("Failed to get video format: %s", strerror(errno)); + if ( vidioctl( vid_fd, VIDIOC_G_FMT, &v4l2_data.fmt ) < 0 ) + Fatal("Failed to get video format: %s", strerror(errno)); - Debug(4, - " v4l2_data.fmt.type = %08x\n" - " v4l2_data.fmt.fmt.pix.width = %d\n" - " v4l2_data.fmt.fmt.pix.height = %d\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" - " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" - " v4l2_data.fmt.fmt.pix.priv = %08x\n" - , v4l2_data.fmt.type - , v4l2_data.fmt.fmt.pix.width - , v4l2_data.fmt.fmt.pix.height - , v4l2_data.fmt.fmt.pix.pixelformat - , v4l2_data.fmt.fmt.pix.field - , v4l2_data.fmt.fmt.pix.bytesperline - , v4l2_data.fmt.fmt.pix.sizeimage - , v4l2_data.fmt.fmt.pix.colorspace - , v4l2_data.fmt.fmt.pix.priv - ); + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %d\n" + " v4l2_data.fmt.fmt.pix.height = %d\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); - v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - v4l2_data.fmt.fmt.pix.width = width; - v4l2_data.fmt.fmt.pix.height = height; - v4l2_data.fmt.fmt.pix.pixelformat = palette; + v4l2_data.fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_data.fmt.fmt.pix.width = width; + v4l2_data.fmt.fmt.pix.height = height; + v4l2_data.fmt.fmt.pix.pixelformat = palette; - if ( (extras & 0xff) != 0 ) { - v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); + if ((extras & 0xff) != 0) { + v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); - v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Fatal("Failed to set video format: %s", strerror(errno)); - } - } - } else { - if ( vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0 ) { - Error("Failed to set video format: %s", strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Warning("Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff)); + v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Fatal("Failed to set video format: %s", strerror(errno)); } } - - /* Note VIDIOC_S_FMT may change width and height. */ - Debug(4, - " v4l2_data.fmt.type = %08x\n" - " v4l2_data.fmt.fmt.pix.width = %d\n" - " v4l2_data.fmt.fmt.pix.height = %d\n" - " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" - " v4l2_data.fmt.fmt.pix.field = %08x\n" - " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" - " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" - " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" - " v4l2_data.fmt.fmt.pix.priv = %08x\n" - , v4l2_data.fmt.type - , v4l2_data.fmt.fmt.pix.width - , v4l2_data.fmt.fmt.pix.height - , v4l2_data.fmt.fmt.pix.pixelformat - , v4l2_data.fmt.fmt.pix.field - , v4l2_data.fmt.fmt.pix.bytesperline - , v4l2_data.fmt.fmt.pix.sizeimage - , v4l2_data.fmt.fmt.pix.colorspace - , 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"); + } else { + if (vidioctl(vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt) < 0) { + Error("Failed to set video format: %s", strerror(errno)); } + } - /* Buggy driver paranoia. */ - unsigned int min; - min = v4l2_data.fmt.fmt.pix.width * 2; - if ( v4l2_data.fmt.fmt.pix.bytesperline < min ) - v4l2_data.fmt.fmt.pix.bytesperline = min; - min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; - if ( v4l2_data.fmt.fmt.pix.sizeimage < min ) - v4l2_data.fmt.fmt.pix.sizeimage = min; + /* Note VIDIOC_S_FMT may change width and height. */ + Debug(4, + " v4l2_data.fmt.type = %08x\n" + " v4l2_data.fmt.fmt.pix.width = %d\n" + " v4l2_data.fmt.fmt.pix.height = %d\n" + " v4l2_data.fmt.fmt.pix.pixelformat = %08x\n" + " v4l2_data.fmt.fmt.pix.field = %08x\n" + " v4l2_data.fmt.fmt.pix.bytesperline = %d\n" + " v4l2_data.fmt.fmt.pix.sizeimage = %d\n" + " v4l2_data.fmt.fmt.pix.colorspace = %08x\n" + " v4l2_data.fmt.fmt.pix.priv = %08x\n" + , v4l2_data.fmt.type + , v4l2_data.fmt.fmt.pix.width + , v4l2_data.fmt.fmt.pix.height + , v4l2_data.fmt.fmt.pix.pixelformat + , v4l2_data.fmt.fmt.pix.field + , v4l2_data.fmt.fmt.pix.bytesperline + , v4l2_data.fmt.fmt.pix.sizeimage + , v4l2_data.fmt.fmt.pix.colorspace + , v4l2_data.fmt.fmt.pix.priv + ); - if ( palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG ) { - v4l2_jpegcompression jpeg_comp; - if ( vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0 ) { - if ( errno == EINVAL ) { - Debug(2, "JPEG compression options are not available"); - } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno)); - } + 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; + if (v4l2_data.fmt.fmt.pix.bytesperline < min) + v4l2_data.fmt.fmt.pix.bytesperline = min; + min = v4l2_data.fmt.fmt.pix.bytesperline * v4l2_data.fmt.fmt.pix.height; + if (v4l2_data.fmt.fmt.pix.sizeimage < min) + v4l2_data.fmt.fmt.pix.sizeimage = min; + + if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + v4l2_jpegcompression jpeg_comp; + if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) { + if (errno == EINVAL) { + Debug(2, "JPEG compression options are not available"); } else { - /* Set flags and quality. MJPEG should not have the huffman tables defined */ - if ( palette == V4L2_PIX_FMT_MJPEG ) { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; - } else { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; - } - jpeg_comp.quality = 85; - - /* Update the JPEG options */ - if ( vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno)); - } else { - 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, markers: %#x", - jpeg_comp.quality, jpeg_comp.jpeg_markers); - } - } - } - } // end if JPEG/MJPEG - - Debug(3, "Setting up request buffers"); - - memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); - if ( channel_count > 1 ) { - Debug(3, "Channel count is %d", channel_count); - if ( v4l_multi_buffer ){ - v4l2_data.reqbufs.count = 2*channel_count; - } else { - v4l2_data.reqbufs.count = 1; + Warning("Failed to get JPEG compression options: %s", strerror(errno)); } } else { - v4l2_data.reqbufs.count = 8; - } - Debug(3, "Request buffers count is %d", v4l2_data.reqbufs.count); - - v4l2_data.reqbufs.type = v4l2_data.fmt.type; - v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; - - if ( vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0 ) { - if ( errno == EINVAL ) { - Fatal("Unable to initialise memory mapping, unsupported in device"); + /* Set flags and quality. MJPEG should not have the huffman tables defined */ + if (palette == V4L2_PIX_FMT_MJPEG) { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; } else { - Fatal("Unable to initialise memory mapping: %s", strerror(errno)); + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; + } + jpeg_comp.quality = 85; + + /* Update the JPEG options */ + if (vidioctl(vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp) < 0) { + Warning("Failed to set JPEG compression options: %s", strerror(errno)); + } else { + 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, markers: %#x", + jpeg_comp.quality, jpeg_comp.jpeg_markers); + } } } + } // end if JPEG/MJPEG - if ( v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1) ) - Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); + Debug(3, "Setting up request buffers"); - Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", - channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); - - v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; -#if HAVE_LIBSWSCALE - capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; -#endif // HAVE_LIBSWSCALE - for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { - struct v4l2_buffer vid_buf; - - memset(&vid_buf, 0, sizeof(vid_buf)); - - //vid_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - vid_buf.type = v4l2_data.fmt.type; - //vid_buf.memory = V4L2_MEMORY_MMAP; - vid_buf.memory = v4l2_data.reqbufs.memory; - vid_buf.index = i; - - if ( vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0 ) - Fatal("Unable to query video buffer: %s", strerror(errno)); - - v4l2_data.buffers[i].length = vid_buf.length; - v4l2_data.buffers[i].start = mmap(nullptr, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset); - - if ( v4l2_data.buffers[i].start == MAP_FAILED ) - Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)", - i, vid_buf.length, strerror(errno), errno); - -#if HAVE_LIBSWSCALE -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); -#else - capturePictures[i] = avcodec_alloc_frame(); -#endif - if ( !capturePictures[i] ) - Fatal("Could not allocate picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays( - capturePictures[i]->data, - capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start, - capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, - 1); -#else - avpicture_fill( - (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, - capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height - ); -#endif -#endif // HAVE_LIBSWSCALE - } // end foreach request buf - - Debug(3, "Configuring video source"); - - if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0 ) { - Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); + memset(&v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs)); + if (channel_count > 1) { + Debug(3, "Channel count is %d", channel_count); + if (v4l_multi_buffer){ + v4l2_data.reqbufs.count = 2*channel_count; + } else { + v4l2_data.reqbufs.count = 1; } - - struct v4l2_input input; - v4l2_std_id stdId; - - memset(&input, 0, sizeof(input)); - input.index = channel; - - if ( vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0 ) { - Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); - } - - if ( (input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN) ) { - Error("Device does not support video standard %d", standard); - } - - stdId = standard; - if ((vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0)) { - Error("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); - } - - Contrast(contrast); - Brightness(brightness); - Hue(hue); - Colour(colour); + } else { + v4l2_data.reqbufs.count = 8; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - Debug(3, "Configuring picture attributes"); + Debug(3, "Request buffers count is %d", v4l2_data.reqbufs.count); - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(vid_pic)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal("Failed to get picture attributes: %s", strerror(errno)); + v4l2_data.reqbufs.type = v4l2_data.fmt.type; + v4l2_data.reqbufs.memory = V4L2_MEMORY_MMAP; - Debug(4, - "Old Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast - ); - - switch (vid_pic.palette = palette) { - case VIDEO_PALETTE_RGB32 : - vid_pic.depth = 32; - break; - case VIDEO_PALETTE_RGB24 : - vid_pic.depth = 24; - break; - case VIDEO_PALETTE_GREY : - vid_pic.depth = 8; - break; - case VIDEO_PALETTE_RGB565 : - case VIDEO_PALETTE_YUYV : - case VIDEO_PALETTE_YUV422 : - case VIDEO_PALETTE_YUV420P : - case VIDEO_PALETTE_YUV422P : - default: - vid_pic.depth = 16; - break; + if (vidioctl(vid_fd, VIDIOC_REQBUFS, &v4l2_data.reqbufs) < 0) { + if (errno == EINVAL) { + Fatal("Unable to initialise memory mapping, unsupported in device"); + } else { + Fatal("Unable to initialise memory mapping: %s", strerror(errno)); } + } - if ( brightness >= 0 ) vid_pic.brightness = brightness; - if ( hue >= 0 ) vid_pic.hue = hue; - if ( colour >= 0 ) vid_pic.colour = colour; - if ( contrast >= 0 ) vid_pic.contrast = contrast; + if (v4l2_data.reqbufs.count < (v4l_multi_buffer?2:1)) + Fatal("Insufficient buffer memory %d on video device", v4l2_data.reqbufs.count); - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } + Debug(3, "Setting up data buffers: Channels %d MultiBuffer %d Buffers: %d", + channel_count, v4l_multi_buffer, v4l2_data.reqbufs.count); - Debug(3, "Configuring window attributes"); + v4l2_data.buffers = new V4L2MappedBuffer[v4l2_data.reqbufs.count]; + capturePictures = new AVFrame *[v4l2_data.reqbufs.count]; - struct video_window vid_win; - memset(&vid_win, 0, sizeof(vid_win)); - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { - Fatal("Failed to get window attributes: %s", strerror(errno)); - } - Debug(4, "Old X:%d Y:%d W:%d H:%d", - vid_win.x, vid_win.y, vid_win.width, vid_win.height); + for (unsigned int i = 0; i < v4l2_data.reqbufs.count; i++) { + struct v4l2_buffer vid_buf; - vid_win.x = 0; - vid_win.y = 0; - vid_win.width = width; - vid_win.height = height; - vid_win.flags &= ~VIDEO_WINDOW_INTERLACE; + memset(&vid_buf, 0, sizeof(vid_buf)); - if ( ioctl(vid_fd, VIDIOCSWIN, &vid_win) < 0 ) { - Error("Failed to set window attributes: %s", strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } + //vid_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vid_buf.type = v4l2_data.fmt.type; + //vid_buf.memory = V4L2_MEMORY_MMAP; + vid_buf.memory = v4l2_data.reqbufs.memory; + vid_buf.index = i; - Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", - vid_win.width, vid_win.height, vid_win.flags); + if (vidioctl(vid_fd, VIDIOC_QUERYBUF, &vid_buf) < 0) + Fatal("Unable to query video buffer: %s", strerror(errno)); - Debug(3, "Setting up request buffers"); - if ( ioctl(vid_fd, VIDIOCGMBUF, &v4l1_data.frames) < 0 ) - Fatal("Failed to setup memory: %s", strerror(errno)); - if ( channel_count > 1 && !v4l_multi_buffer ) - v4l1_data.frames.frames = 1; - v4l1_data.buffers = new video_mmap[v4l1_data.frames.frames]; - Debug(4, "vmb.frames = %d, vmb.size = %d", - v4l1_data.frames.frames, v4l1_data.frames.size); + v4l2_data.buffers[i].length = vid_buf.length; + v4l2_data.buffers[i].start = mmap(nullptr, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset); - Debug(3, "Setting up %d frame buffers", v4l1_data.frames.frames); + if (v4l2_data.buffers[i].start == MAP_FAILED) + Fatal("Can't map video buffer %u (%u bytes) to memory: %s(%d)", + i, vid_buf.length, strerror(errno), errno); - v4l1_data.bufptr = (unsigned char *)mmap(0, v4l1_data.frames.size, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, 0); - if ( v4l1_data.bufptr == MAP_FAILED ) - Fatal("Could not mmap video: %s", strerror(errno)); + capturePictures[i] = av_frame_alloc(); -#if HAVE_LIBSWSCALE - capturePictures = new AVFrame *[v4l1_data.frames.frames]; - for ( int i = 0; i < v4l1_data.frames.frames; i++ ) { - v4l1_data.buffers[i].frame = i; - v4l1_data.buffers[i].width = width; - v4l1_data.buffers[i].height = height; - v4l1_data.buffers[i].format = palette; + if (!capturePictures[i]) + Fatal("Could not allocate picture"); -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); -#else - capturePictures[i] = avcodec_alloc_frame(); -#endif - if ( !capturePictures[i] ) - Fatal("Could not allocate picture"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays( - capturePictures[i]->data, - capturePictures[i]->linesize, - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height, 1); -#else - avpicture_fill( - (AVPicture *)capturePictures[i], - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height ); -#endif - } -#endif // HAVE_LIBSWSCALE + av_image_fill_arrays( + capturePictures[i]->data, + capturePictures[i]->linesize, + (uint8_t*)v4l2_data.buffers[i].start, + capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height, + 1); + } // end foreach request buf - Debug(3, "Configuring video source"); + Debug(3, "Configuring video source"); - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(vid_src)); - vid_src.channel = channel; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) - Fatal("Failed to get camera source: %s", strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channel) < 0) { + Fatal("Failed to set camera source %d: %s", channel, strerror(errno)); + } - Debug(4, "Old C:%d, F:%d, Fl:%x, T:%d", - vid_src.channel, vid_src.norm, vid_src.flags, vid_src.type); + struct v4l2_input input; + v4l2_std_id stdId; - vid_src.norm = standard; - vid_src.flags = 0; - vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { - Error("Failed to set camera source %d: %s", channel, strerror(errno)); - if ( config.strict_video_config ) - exit(-1); - } + memset(&input, 0, sizeof(input)); + input.index = channel; - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) - Fatal("Failed to get window data: %s", strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_ENUMINPUT, &input) < 0) { + Fatal("Failed to enumerate input %d: %s", channel, strerror(errno)); + } - Info("vid_win.width = %08x, vid_win.height = %08x, vid_win.flags = %08x", - vid_win.width, vid_win.height, vid_win.flags); + if ((input.std != V4L2_STD_UNKNOWN) && ((input.std & standard) == V4L2_STD_UNKNOWN)) { + Error("Device does not support video standard %d", standard); + } - Debug(4, "New X:%d Y:%d W:%d H:%d", - vid_win.x, vid_win.y, vid_win.width, vid_win.height); + stdId = standard; + if ((vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0)) { + Error("Failed to set video standard %d: %d %s", standard, errno, strerror(errno)); + } - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) - Fatal("Failed to get window data: %s", strerror(errno)); - - Debug(4, - "New Palette:%d, depth:%d, brightness:%d, hue:%d, colour:%d, contrast:%d", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast - ); - } // end if v4l -#endif // ZM_HAS_V4L1 + Contrast(contrast); + Brightness(brightness); + Hue(hue); + Colour(colour); } // end LocalCamera::Initialize void LocalCamera::Terminate() { -#if ZM_HAS_V4L2 if ( v4l_version == 2 ) { Debug(3, "Terminating video stream"); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -1138,40 +738,12 @@ void LocalCamera::Terminate() { Debug(3, "Unmapping video buffers"); for ( unsigned int i = 0; i < v4l2_data.reqbufs.count; i++ ) { -#if HAVE_LIBSWSCALE - /* Free capture pictures */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free(&capturePictures[i]); -#else - av_freep(&capturePictures[i]); -#endif -#endif + if ( munmap(v4l2_data.buffers[i].start, v4l2_data.buffers[i].length) < 0 ) Error("Failed to munmap buffer %d: %s", i, strerror(errno)); } } -#endif // ZM_HAS_V4L2 - -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { -#if HAVE_LIBSWSCALE - for ( int i=0; i < v4l1_data.frames.frames; i++ ) { - /* Free capture pictures */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free(&capturePictures[i]); -#else - av_freep(&capturePictures[i]); -#endif - } -#endif - - Debug(3, "Unmapping video buffers"); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error("Failed to munmap buffers: %s", strerror(errno)); - - delete[] v4l1_data.buffers; - } // end if using v4l1 -#endif // ZM_HAS_V4L1 close(vid_fd); primed = false; @@ -1180,7 +752,6 @@ void LocalCamera::Terminate() { uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Automatic format selection */ uint32_t selected_palette = 0; -#if ZM_HAS_V4L2 char fmt_desc[64][32]; uint32_t fmt_fcc[64]; v4l2_fmtdesc fmtinfo; @@ -1208,14 +779,14 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Got a format. Copy it to the array */ strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - + Debug(3, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", - fmt_desc[nIndex], - (fmt_fcc[nIndex]>>24)&0xff, - (fmt_fcc[nIndex]>>16)&0xff, - (fmt_fcc[nIndex]>>8)&0xff, - (fmt_fcc[nIndex])&0xff, - nIndex); + fmt_desc[nIndex], + static_cast((fmt_fcc[nIndex] >> 24) & 0xff), + static_cast((fmt_fcc[nIndex] >> 16) & 0xff), + static_cast((fmt_fcc[nIndex] >> 8) & 0xff), + static_cast((fmt_fcc[nIndex]) & 0xff), + nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); @@ -1244,13 +815,23 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { for ( unsigned int j=0; j < nIndex; j++ ) { if ( preferedformats[i] == fmt_fcc[j] ) { Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", - fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + fmt_desc[j], + static_cast(fmt_fcc[j] & 0xff), + static_cast((fmt_fcc[j] >> 8) & 0xff), + static_cast((fmt_fcc[j] >> 16) & 0xff), + static_cast((fmt_fcc[j] >> 24) & 0xff), + j); /* Found a format! */ nIndexUsed = j; break; } else { Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", - fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + fmt_desc[j], + static_cast(fmt_fcc[j] & 0xff), + static_cast((fmt_fcc[j] >> 8) & 0xff), + static_cast((fmt_fcc[j] >> 16) & 0xff), + static_cast((fmt_fcc[j] >> 24) & 0xff), + j); } } } @@ -1265,7 +846,6 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Close the device */ close(enum_fd); -#endif /* ZM_HAS_V4L2 */ return selected_palette; } //uint32_t LocalCamera::AutoSelectFormat(int p_colours) @@ -1273,54 +853,56 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { (test) ? (prefix yesString " " capability "\n") : (prefix noString " " capability "\n") bool LocalCamera::GetCurrentSettings( - const char *device, + const std::string& device, char *output, int version, bool verbose) { output[0] = 0; char *output_ptr = output; - char queryDevice[PATH_MAX] = ""; + std::string queryDevice; int devIndex = 0; do { - if ( device ) { - strncpy(queryDevice, device, sizeof(queryDevice)-1); + if (!device.empty()) { + queryDevice = device; } else { - sprintf(queryDevice, "/dev/video%d", devIndex); + queryDevice = stringtf("/dev/video%d", devIndex); } - if ( (vid_fd = open(queryDevice, O_RDWR)) <= 0 ) { - if ( device ) { - Error("Failed to open video device %s: %s", queryDevice, strerror(errno)); - if ( verbose ) + if ((vid_fd = open(queryDevice.c_str(), O_RDWR)) <= 0) { + if (!device.empty()) { + Error("Failed to open video device %s: %s", queryDevice.c_str(), strerror(errno)); + if (verbose) { output_ptr += sprintf(output_ptr, "Error, failed to open video device %s: %s\n", - queryDevice, strerror(errno)); - else + queryDevice.c_str(), strerror(errno)); + } else { output_ptr += sprintf(output_ptr, "error%d\n", errno); + } return false; } else { return true; } } - if ( verbose ) { - output_ptr += sprintf(output_ptr, "Video Device: %s\n", queryDevice); + + if (verbose) { + output_ptr += sprintf(output_ptr, "Video Device: %s\n", queryDevice.c_str()); } else { - output_ptr += sprintf(output_ptr, "d:%s|", queryDevice); + output_ptr += sprintf(output_ptr, "d:%s|", queryDevice.c_str()); } -#if ZM_HAS_V4L2 - if ( version == 2 ) { - struct v4l2_capability vid_cap; - if ( vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0 ) { + if (version == 2) { + v4l2_capability vid_cap = {}; + if (vidioctl(vid_fd, VIDIOC_QUERYCAP, &vid_cap) < 0) { Error("Failed to query video device: %s", strerror(errno)); - if ( verbose ) { + if (verbose) { output_ptr += sprintf(output_ptr, "Error, failed to query video capabilities %s: %s\n", - queryDevice, strerror(errno)); + queryDevice.c_str(), strerror(errno)); } else { output_ptr += sprintf(output_ptr, "error%d\n", errno); } - if ( device ) + if (!device.empty()) { return false; + } } if ( verbose ) { @@ -1363,7 +945,7 @@ bool LocalCamera::GetCurrentSettings( output_ptr += sprintf(output_ptr, verbose ? " Standards:\n" : "S:"); - struct v4l2_standard standard; + v4l2_standard standard = {}; int standardIndex = 0; do { memset(&standard, 0, sizeof(standard)); @@ -1391,10 +973,10 @@ bool LocalCamera::GetCurrentSettings( *(output_ptr-1) = '|'; output_ptr += sprintf(output_ptr, verbose ? " Formats:\n" : "F:"); - struct v4l2_fmtdesc format; + int formatIndex = 0; do { - memset(&format, 0, sizeof(format)); + v4l2_fmtdesc format = {}; format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.index = formatIndex; @@ -1414,20 +996,20 @@ bool LocalCamera::GetCurrentSettings( 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 ) @@ -1435,9 +1017,9 @@ bool LocalCamera::GetCurrentSettings( else output_ptr += sprintf(output_ptr, "Crop Capabilities\n"); - struct v4l2_cropcap cropcap; - memset(&cropcap, 0, sizeof(cropcap)); + v4l2_cropcap cropcap = {}; cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ( vidioctl(vid_fd, VIDIOC_CROPCAP, &cropcap) < 0 ) { if ( errno != EINVAL ) { /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ @@ -1451,8 +1033,8 @@ bool LocalCamera::GetCurrentSettings( output_ptr += sprintf(output_ptr, "B:%dx%d|", 0, 0); } } else { - struct v4l2_crop crop; - memset(&crop, 0, sizeof(crop)); + v4l2_crop crop = {}; + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if ( vidioctl(vid_fd, VIDIOC_G_CROP, &crop) < 0 ) { @@ -1537,17 +1119,17 @@ bool LocalCamera::GetCurrentSettings( " Name: %s\n" " Type: %s\n" " Audioset: %08x\n" - " Standards: 0x%llx\n" + " Standards: 0x%" PRIx64"\n" , input.index , input.name , input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") , input.audioset - , input.std ); + , static_cast(input.std)); } else { - output_ptr += sprintf( output_ptr, "i%d:%s|i%dT:%s|i%dS:%llx|" + output_ptr += sprintf( output_ptr, "i%d:%s|i%dT:%s|i%dS:%" PRIx64 "|" , input.index, input.name , input.index, input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") - , input.index, input.std); + , input.index, static_cast(input.std)); } if ( verbose ) { @@ -1567,649 +1149,205 @@ bool LocalCamera::GetCurrentSettings( if ( !verbose ) *(output_ptr-1) = '\n'; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( version == 1 ) { - struct video_capability vid_cap; - memset(&vid_cap, 0, sizeof(video_capability)); - if ( ioctl(vid_fd, VIDIOCGCAP, &vid_cap) < 0 ) { - Error("Failed to get video capabilities: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, - "Error, failed to get video capabilities %s: %s\n", - queryDevice, strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf( output_ptr, "Video Capabilities\n" - " Name: %s\n" - " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s" - " Video Channels: %d\n" - " Audio Channels: %d\n" - " Maximum Width: %d\n" - " Maximum Height: %d\n" - " Minimum Width: %d\n" - " Minimum Height: %d\n", - vid_cap.name, - vid_cap.type, - (vid_cap.type&VID_TYPE_CAPTURE)?" Can capture\n":"", - (vid_cap.type&VID_TYPE_TUNER)?" Can tune\n":"", - (vid_cap.type&VID_TYPE_TELETEXT)?" Does teletext\n":"", - (vid_cap.type&VID_TYPE_OVERLAY)?" Overlay onto frame buffer\n":"", - (vid_cap.type&VID_TYPE_CHROMAKEY)?" Overlay by chromakey\n":"", - (vid_cap.type&VID_TYPE_CLIPPING)?" Can clip\n":"", - (vid_cap.type&VID_TYPE_FRAMERAM)?" Uses the frame buffer memory\n":"", - (vid_cap.type&VID_TYPE_SCALES)?" Scalable\n":"", - (vid_cap.type&VID_TYPE_MONOCHROME)?" Monochrome only\n":"", - (vid_cap.type&VID_TYPE_SUBCAPTURE)?" Can capture subareas of the image\n":"", - (vid_cap.type&VID_TYPE_MPEG_DECODER)?" Can decode MPEG streams\n":"", - (vid_cap.type&VID_TYPE_MPEG_ENCODER)?" Can encode MPEG streams\n":"", - (vid_cap.type&VID_TYPE_MJPEG_DECODER)?" Can decode MJPEG streams\n":"", - (vid_cap.type&VID_TYPE_MJPEG_ENCODER)?" Can encode MJPEG streams\n":"", - vid_cap.channels, - vid_cap.audios, - vid_cap.maxwidth, - vid_cap.maxheight, - vid_cap.minwidth, - vid_cap.minheight ); - } else { - output_ptr += sprintf(output_ptr, "N:%s|T:%d|nC:%d|nA:%d|mxW:%d|mxH:%d|mnW:%d|mnH:%d|" - , vid_cap.name - , vid_cap.type - , vid_cap.channels - , vid_cap.audios - , vid_cap.maxwidth - , vid_cap.maxheight - , vid_cap.minwidth - , vid_cap.minheight); - } - struct video_window vid_win; - memset(&vid_win, 0, sizeof(video_window)); - if ( ioctl(vid_fd, VIDIOCGWIN, &vid_win) < 0 ) { - Error("Failed to get window attributes: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get window attributes: %s\n", strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Window Attributes\n" - " X Offset: %d\n" - " Y Offset: %d\n" - " Width: %d\n" - " Height: %d\n" - , vid_win.x - , vid_win.y - , vid_win.width - , vid_win.height ); - } else { - output_ptr += sprintf(output_ptr, "X:%d|Y:%d|W:%d|H:%d|", - vid_win.height, vid_win.x, vid_win.y, vid_win.width); - } - - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get picture attributes: %s\n", strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Picture Attributes\n" - " Palette: %d - %s\n" - " Colour Depth: %d\n" - " Brightness: %d\n" - " Hue: %d\n" - " Colour :%d\n" - " Contrast: %d\n" - " Whiteness: %d\n" - , vid_pic.palette, - vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( - vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( - vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( - vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( - vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( - vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( - vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" - ))))))))))))))))), - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast, - vid_pic.whiteness - ); - } else { - output_ptr += sprintf(output_ptr, "P:%d|D:%d|B:%d|h:%d|Cl:%d|Cn:%d|w:%d|", - vid_pic.palette, - vid_pic.depth, - vid_pic.brightness, - vid_pic.hue, - vid_pic.colour, - vid_pic.contrast, - vid_pic.whiteness - ); - } - - for ( int chan = 0; chan < vid_cap.channels; chan++ ) { - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(video_channel)); - vid_src.channel = chan; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { - Error("Failed to get channel %d attributes: %s", chan, strerror(errno)); - if ( verbose ) - output_ptr += sprintf(output_ptr, "Error, failed to get channel %d attributes: %s\n", chan, strerror(errno)); - else - output_ptr += sprintf(output_ptr, "error%d\n", errno); - return false; - } - if ( verbose ) { - output_ptr += sprintf(output_ptr, - "Channel %d Attributes\n" - " Name: %s\n" - " Channel: %d\n" - " Flags: %d\n%s%s" - " Type: %d - %s\n" - " Format: %d - %s\n" - , chan - , vid_src.name - , vid_src.channel - , vid_src.flags - , (vid_src.flags&VIDEO_VC_TUNER)?" Channel has a tuner\n":"" - , (vid_src.flags&VIDEO_VC_AUDIO)?" Channel has audio\n":"" - , vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - ) - , vid_src.norm, - vid_src.norm==VIDEO_MODE_PAL?"PAL":( - vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( - vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( - vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" - )))); - } else { - output_ptr += sprintf(output_ptr, "n%d:%s|C%d:%d|Fl%d:%x|T%d:%d|F%d:%d%s|" - , chan, vid_src.name - , chan, vid_src.channel - , chan, vid_src.flags - , chan, vid_src.type - , chan, vid_src.norm, chan==(vid_cap.channels-1)?"":"," - ); - } - } - if ( !verbose ) - *output_ptr = '\n'; - } -#endif // ZM_HAS_V4L1 close(vid_fd); - if ( device ) + if (!device.empty()) { break; + } } while ( ++devIndex < 32 ); return true; } -int LocalCamera::Brightness(int p_brightness) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; +int LocalCamera::Control(int vid_id, int newvalue) { + struct v4l2_control vid_control; - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_BRIGHTNESS; + memset(&vid_control, 0, sizeof(vid_control)); + vid_control.id = vid_id; - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query brightness: %s", strerror(errno)); + if (vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0) { + if (errno != EINVAL) { + Error("Unable to query control: %s", strerror(errno)); + } else { + Warning("Control is not supported"); + } + } else if (newvalue >= 0) { + vid_control.value = newvalue; + + /* The driver may clamp the value or return ERANGE, ignored here */ + if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { + if (errno != ERANGE) { + Error("Unable to set control: %s", strerror(errno)); } else { - Warning("Brightness control is not supported"); + Warning("Given control value (%d) may be out-of-range", newvalue); } - //Info( "Brightness 1 %d", vid_control.value ); - } else if ( p_brightness >= 0 ) { - vid_control.value = p_brightness; - - //Info( "Brightness 2 %d", vid_control.value ); - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { - if ( errno != ERANGE ) { - Error("Unable to set brightness: %s", strerror(errno)); - } else { - Warning("Given brightness value (%d) may be out-of-range", p_brightness); - } - } - //Info( "Brightness 3 %d", vid_control.value ); } - return vid_control.value; } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } + return vid_control.value; +} - if ( p_brightness >= 0 ) { - vid_pic.brightness = p_brightness; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.brightness; - } -#endif // ZM_HAS_V4L1 - return -1; +int LocalCamera::Brightness(int p_brightness) { + return Control(V4L2_CID_BRIGHTNESS, p_brightness); } int LocalCamera::Hue(int p_hue) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset( &vid_control, 0, sizeof(vid_control) ); - vid_control.id = V4L2_CID_HUE; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) - Error("Unable to query hue: %s", strerror(errno)) - else - Warning("Hue control is not supported") - } else if ( p_hue >= 0 ) { - vid_control.value = p_hue; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { - if ( errno != ERANGE ) { - Error("Unable to set hue: %s", strerror(errno)); - } else { - Warning("Given hue value (%d) may be out-of-range", p_hue); - } - } - } - return vid_control.value; - } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_hue >= 0 ) { - vid_pic.hue = p_hue; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.hue; - } -#endif // ZM_HAS_V4L1 - return -1; + return Control(V4L2_CID_HUE, p_hue); } int LocalCamera::Colour( int p_colour ) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_SATURATION; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query saturation: %s", strerror(errno)); - } else { - Warning("Saturation control is not supported"); - } - } else if ( p_colour >= 0 ) { - vid_control.value = p_colour; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) < 0 ) { - if ( errno != ERANGE ) { - Error("Unable to set saturation: %s", strerror(errno)); - } else { - Warning("Given saturation value (%d) may be out-of-range", p_colour); - } - } - } - return vid_control.value; - } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_colour >= 0 ) { - vid_pic.colour = p_colour; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.colour; - } -#endif // ZM_HAS_V4L1 - return -1; + return Control(V4L2_CID_SATURATION, p_colour); } -int LocalCamera::Contrast( int p_contrast ) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - struct v4l2_control vid_control; - - memset(&vid_control, 0, sizeof(vid_control)); - vid_control.id = V4L2_CID_CONTRAST; - - if ( vidioctl(vid_fd, VIDIOC_G_CTRL, &vid_control) < 0 ) { - if ( errno != EINVAL ) { - Error("Unable to query contrast: %s", strerror(errno)); - } else { - Warning("Contrast control is not supported"); - } - } else if ( p_contrast >= 0 ) { - vid_control.value = p_contrast; - - /* The driver may clamp the value or return ERANGE, ignored here */ - if ( vidioctl(vid_fd, VIDIOC_S_CTRL, &vid_control) ) { - if ( errno != ERANGE ) { - Error("Unable to set contrast: %s", strerror(errno)); - } else { - Warning("Given contrast value (%d) may be out-of-range", p_contrast); - } - } - } - return vid_control.value; - } -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - struct video_picture vid_pic; - memset(&vid_pic, 0, sizeof(video_picture)); - if ( ioctl(vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) { - Error("Failed to get picture attributes: %s", strerror(errno)); - return -1; - } - - if ( p_contrast >= 0 ) { - vid_pic.contrast = p_contrast; - if ( ioctl(vid_fd, VIDIOCSPICT, &vid_pic) < 0 ) { - Error("Failed to set picture attributes: %s", strerror(errno)); - return -1; - } - } - return vid_pic.contrast; - } -#endif // ZM_HAS_V4L1 - return -1; +int LocalCamera::Contrast(int p_contrast) { + return Control(V4L2_CID_CONTRAST, p_contrast); } int LocalCamera::PrimeCapture() { - get_VideoStream(); - if ( !device_prime ) + getVideoStream(); + if (!device_prime) return 1; - Debug(2, "Priming capture"); -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - 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; + 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) { - Error("Failed to queue buffer %d: %s", frame, strerror(errno)); - return 0; - } + 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); } - v4l2_data.bufptr = nullptr; - Debug(3, "Starting video stream"); - //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) { - 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 ) { - for ( int frame = 0; frame < v4l1_data.frames.frames; frame++ ) { - Debug(3, "Queueing frame %d", frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[frame]) < 0 ) { - Error("Capture failure for frame %d: %s", frame, strerror(errno)); - return -1; - } + 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) { + Error("Failed to queue buffer %d: %s", frame, strerror(errno)); + return 0; } } -#endif // ZM_HAS_V4L1 + v4l2_data.bufptr = nullptr; + + Debug(3, "Starting video stream"); + //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) { + Error("Failed to start capture stream: %s", strerror(errno)); + return -1; + } return 1; } // end LocalCamera::PrimeCapture int LocalCamera::PreCapture() { - //Debug(5, "Pre-capturing"); return 1; } -int LocalCamera::Capture(ZMPacket &zm_packet) { +int LocalCamera::Capture(std::shared_ptr &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; int captures_per_frame = 1; - if ( channel_count > 1 ) + if (channel_count > 1) captures_per_frame = v4l_captures_per_frame; - if ( captures_per_frame <= 0 ) { + if (captures_per_frame <= 0) { captures_per_frame = 1; Warning("Invalid Captures Per Frame setting: %d", captures_per_frame); } // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer - if ( channel_prime ) { -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - static struct v4l2_buffer vid_buf; + if (channel_prime) { + static struct v4l2_buffer vid_buf; - memset(&vid_buf, 0, sizeof(vid_buf)); + memset(&vid_buf, 0, sizeof(vid_buf)); - vid_buf.type = v4l2_data.fmt.type; - vid_buf.memory = v4l2_data.reqbufs.memory; + vid_buf.type = v4l2_data.fmt.type; + vid_buf.memory = v4l2_data.reqbufs.memory; - Debug(3, "Capturing %d frames", captures_per_frame); - while ( captures_per_frame ) { - if ( vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0 ) { - if ( errno == EIO ) { - Warning("Capture failure, possible signal loss?: %s", strerror(errno)); - } else { - Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); - } - return -1; + Debug(3, "Capturing %d frames", captures_per_frame); + while (captures_per_frame) { + if (vidioctl(vid_fd, VIDIOC_DQBUF, &vid_buf) < 0) { + if (errno == EIO) { + Warning("Capture failure, possible signal loss?: %s", strerror(errno)); + } else { + Error("Unable to capture frame %d: %s", vid_buf.index, strerror(errno)); } - Debug(5, "Captured a frame"); - - v4l2_data.bufptr = &vid_buf; - capture_frame = v4l2_data.bufptr->index; - bytes += vid_buf.bytesused; - - if ( --captures_per_frame ) { - if ( vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0 ) { - Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); - return -1; - } - } - } // while captures_per_frame - - Debug(3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel); - - buffer = (unsigned char *)v4l2_data.buffers[v4l2_data.bufptr->index].start; - 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); - } - } // end if v4l2 -#if ZM_HAS_V4L1 - else -#endif // ZM_HAS_V4L1 -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - if ( v4l_version == 1 ) { - Debug(3, "Capturing %d frames", captures_per_frame); - while ( captures_per_frame ) { - Debug(3, "Syncing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCSYNC, &v4l1_data.active_frame) < 0 ) { - Error("Sync failure for frame %d buffer %d: %s", - v4l1_data.active_frame, captures_per_frame, strerror(errno) ); - return -1; - } - captures_per_frame--; - if ( captures_per_frame ) { - Debug(3, "Capturing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { - Error("Capture failure for buffer %d (%d): %s", - v4l1_data.active_frame, captures_per_frame, strerror(errno)); - return -1; - } - } - } - capture_frame = v4l1_data.active_frame; - Debug(3, "Captured %d for channel %d", capture_frame, channel); - - buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; - } -#endif // ZM_HAS_V4L1 -#if ZM_HAS_V4L2 - if ( v4l_version == 2 ) { - if ( channel_count > 1 ) { - int next_channel = (channel_index+1)%channel_count; - Debug(3, "Switching video source to %d", channels[next_channel]); - if ( vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0 ) { - Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); - return -1; - } - - v4l2_std_id stdId = standards[next_channel]; - if ( vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0 ) { - Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); - } - } - if ( v4l2_data.bufptr ) { - Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index); - if ( vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0 ) { - Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno)); - return -1; - } - } else { - 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 ) { - if ( channel_count > 1 ) { - Debug(3, "Switching video source"); - int next_channel = (channel_index+1)%channel_count; - struct video_channel vid_src; - memset(&vid_src, 0, sizeof(vid_src)); - vid_src.channel = channel; - if ( ioctl(vid_fd, VIDIOCGCHAN, &vid_src) < 0 ) { - Error("Failed to get camera source %d: %s", channel, strerror(errno)); - return -1; - } - - vid_src.channel = channels[next_channel]; - vid_src.norm = standards[next_channel]; - vid_src.flags = 0; - vid_src.type = VIDEO_TYPE_CAMERA; - if ( ioctl(vid_fd, VIDIOCSCHAN, &vid_src) < 0 ) { - Error("Failed to set camera source %d: %s", channel, strerror(errno)); - return -1; - } - } - Debug(3, "Requeueing frame %d", v4l1_data.active_frame); - if ( ioctl(vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame]) < 0 ) { - Error("Capture failure for frame %d: %s", v4l1_data.active_frame, strerror(errno)); return -1; } - v4l1_data.active_frame = (v4l1_data.active_frame+1)%v4l1_data.frames.frames; - } -#endif // ZM_HAS_V4L1 + Debug(5, "Captured a frame"); + v4l2_data.bufptr = &vid_buf; + capture_frame = v4l2_data.bufptr->index; + bytes += vid_buf.bytesused; + + if (--captures_per_frame) { + if (vidioctl(vid_fd, VIDIOC_QBUF, &vid_buf) < 0) { + Error("Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno)); + return -1; + } + } + } // while captures_per_frame + + Debug(3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel); + + buffer = (unsigned char *)v4l2_data.buffers[v4l2_data.bufptr->index].start; + 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 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); + } + + if (channel_count > 1) { + int next_channel = (channel_index+1)%channel_count; + Debug(3, "Switching video source to %d", channels[next_channel]); + if (vidioctl(vid_fd, VIDIOC_S_INPUT, &channels[next_channel]) < 0) { + Error("Failed to set camera source %d: %s", channels[next_channel], strerror(errno)); + return -1; + } + + v4l2_std_id stdId = standards[next_channel]; + if (vidioctl(vid_fd, VIDIOC_S_STD, &stdId) < 0) { + Error("Failed to set video format %d: %s", standards[next_channel], strerror(errno)); + } + } + if (v4l2_data.bufptr) { + Debug(3, "Requeueing buffer %d", v4l2_data.bufptr->index); + if (vidioctl(vid_fd, VIDIOC_QBUF, v4l2_data.bufptr) < 0) { + Error("Unable to requeue buffer %d: %s", v4l2_data.bufptr->index, strerror(errno)); + return -1; + } + } else { + Error("Unable to requeue buffer due to not v4l2_data"); + } } /* prime capture */ - if ( !zm_packet.image ) { - Debug(1, "Allocating image"); - zm_packet.image = new Image(width, height, colours, subpixelorder); + if (!zm_packet->image) { + Debug(4, "Allocating image"); + zm_packet->image = new Image(width, height, colours, subpixelorder); } - if ( conversion_type != 0 ) { + 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 ) { + 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 ) { + 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, @@ -2219,9 +1357,7 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { tmpPicture->data, tmpPicture->linesize ); - } else -#endif - if ( conversion_type == 2 ) { + } else 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); @@ -2229,27 +1365,23 @@ int LocalCamera::Capture(ZMPacket &zm_packet) { // Need to store the jpeg data too Debug(9, "Decoding the JPEG image"); /* JPEG decoding */ - zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); + 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); + zm_packet->image->Assign(width, height, colours, subpixelorder, buffer, imagesize); } // end if doing conversion or not - zm_packet.packet.stream_index = mVideoStreamId; - zm_packet.codec_type = AVMEDIA_TYPE_VIDEO; - zm_packet.keyframe = 1; + 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; - Debug(4, "Post-capturing"); - return 0; } - - -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 6d3ff2672..f702b816f 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -22,22 +22,9 @@ #include "zm_camera.h" -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 -#ifdef HAVE_LINUX_VIDEODEV_H -#include -#endif // HAVE_LINUX_VIDEODEV_H -#ifdef HAVE_LIBV4L1_VIDEODEV_H -#include -#endif // HAVE_LIB4VL1_VIDEODEV_H -#ifdef HAVE_LINUX_VIDEODEV2_H #include -#endif // HAVE_LINUX_VIDEODEV2_H - -// Required on systems with v4l1 but without v4l2 headers -#ifndef VIDEO_MAX_FRAME -#define VIDEO_MAX_FRAME 32 -#endif // // Class representing 'local' cameras, i.e. those which are @@ -46,7 +33,6 @@ // class LocalCamera : public Camera { protected: -#if ZM_HAS_V4L2 struct V4L2MappedBuffer { void *start; size_t length; @@ -60,16 +46,6 @@ protected: V4L2MappedBuffer *buffers; v4l2_buffer *bufptr; }; -#endif // ZM_HAS_V4L2 - -#if ZM_HAS_V4L1 - struct V4L1Data { - int active_frame; - video_mbuf frames; - video_mmap *buffers; - unsigned char *bufptr; - }; -#endif // ZM_HAS_V4L1 protected: std::string device; @@ -95,20 +71,13 @@ protected: bool v4l_multi_buffer; unsigned int v4l_captures_per_frame; -#if ZM_HAS_V4L2 static V4L2Data v4l2_data; -#endif // ZM_HAS_V4L2 -#if ZM_HAS_V4L1 - static V4L1Data v4l1_data; -#endif // ZM_HAS_V4L1 -#if HAVE_LIBSWSCALE static AVFrame **capturePictures; _AVPIXELFORMAT imagePixFormat; _AVPIXELFORMAT capturePixFormat; struct SwsContext *imgConversionContext; AVFrame *tmpPicture; -#endif // HAVE_LIBSWSCALE static LocalCamera *last_camera; @@ -144,6 +113,7 @@ public: int Palette() const { return palette; } int Extras() const { return extras; } + int Control(int vid_id, int newvalue=-1 ); int Brightness( int p_brightness=-1 ) override; int Hue( int p_hue=-1 ) override; int Colour( int p_colour=-1 ) override; @@ -151,13 +121,12 @@ public: int PrimeCapture() override; int PreCapture() override; - int Capture(ZMPacket &p) override; + int Capture(std::shared_ptr &p) override; int PostCapture() override; - int Close() override { return 0; }; - - static bool GetCurrentSettings(const char *device, char *output, int version, bool verbose); + int Close() override; + static bool GetCurrentSettings(const std::string &device, char *output, int version, bool verbose); }; -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 #endif // ZM_LOCAL_CAMERA_H diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index aef3e45fa..6f122d4fc 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -20,11 +20,11 @@ #include "zm_logger.h" #include "zm_db.h" +#include "zm_time.h" #include "zm_utils.h" - #include #include -#include +#include #ifdef __FreeBSD__ #include @@ -43,11 +43,11 @@ Logger::IntMap Logger::smSyslogPriorities; void Logger::usrHandler(int sig) { Logger *logger = fetch(); - if ( sig == SIGUSR1 ) + if (sig == SIGUSR1) logger->level(logger->level()+1); - else if ( sig == SIGUSR2 ) + else if (sig == SIGUSR2) logger->level(logger->level()-1); - Info("Logger - Level changed to %d", logger->level()); + Info("Logger - Level changed to %d %s", logger->level(), smCodes[logger->level()].c_str()); } Logger::Logger() : @@ -126,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 @@ -166,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 == "" ) { @@ -296,25 +296,23 @@ 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; + if (level > NOOPT) { + mLevel = limit(level); mEffectiveLevel = NOLOG; - if ( mTerminalLevel > mEffectiveLevel ) + if (mTerminalLevel > mEffectiveLevel) mEffectiveLevel = mTerminalLevel; - if ( mDatabaseLevel > mEffectiveLevel ) + if (mDatabaseLevel > mEffectiveLevel) mEffectiveLevel = mDatabaseLevel; - if ( mFileLevel > mEffectiveLevel ) + if (mFileLevel > mEffectiveLevel) mEffectiveLevel = mFileLevel; - if ( mSyslogLevel > mEffectiveLevel ) + if (mSyslogLevel > mEffectiveLevel) mEffectiveLevel = mSyslogLevel; - if ( mEffectiveLevel > mLevel) + if (mEffectiveLevel > mLevel) mEffectiveLevel = mLevel; // DEBUG levels should flush - if ( mLevel > INFO ) + if (mLevel > INFO) mFlush = true; } return mLevel; @@ -324,9 +322,7 @@ 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; } @@ -419,7 +415,7 @@ void Logger::closeSyslog() { (void) closelog(); } -void Logger::logPrint(bool hex, const char * const filepath, const int line, const int level, const char *fstring, ...) { +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); @@ -427,31 +423,22 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con 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; const char *base = strrchr(filepath, '/'); const char *file = base ? base+1 : filepath; const char *classString = smCodes[level].c_str(); - gettimeofday(&timeVal, nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); + time_t now_sec = std::chrono::system_clock::to_time_t(now); + Microseconds now_frac = std::chrono::duration_cast( + now.time_since_epoch() - std::chrono::duration_cast(now.time_since_epoch())); -#if 0 - if ( logRuntime ) { - static struct timeval logStart; - - subtractTime( &timeVal, &logStart ); - - snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); - } else { -#endif - char *timePtr = timeString; - timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec)); - snprintf(timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec); -#if 0 - } -#endif + char *timePtr = timeString; + tm now_tm = {}; + timePtr += strftime(timePtr, sizeof(timeString), "%x %H:%M:%S", localtime_r(&now_sec, &now_tm)); + snprintf(timePtr, sizeof(timeString) - (timePtr - timeString), ".%06" PRIi64, static_cast(now_frac.count())); pid_t tid; #ifdef __FreeBSD__ @@ -500,6 +487,11 @@ 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) { @@ -510,32 +502,31 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con if (level <= mFileLevel) { if (!mLogFileFP) { // FIXME unlocking here is a problem. Another thread could sneak in. - log_mutex.unlock(); + // 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) { fputs(logString, mLogFileFP); if (mFlush) fflush(mLogFileFP); - } else { - puts("Logging to file, but failed to open it\n"); + } else if (mTerminalLevel != NOLOG) { + puts("Logging to file but failed to open it\n"); } } // end if level <= mFileLevel if (level <= mDatabaseLevel) { if (zmDbConnected) { - int syslogSize = syslogEnd-syslogStart; - char escapedString[(syslogSize*2)+1]; - mysql_real_escape_string(&dbconn, escapedString, syslogStart, syslogSize); + std::string escapedString = zmDbEscapeString({syslogStart, syslogEnd}); 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 - ); + "( %ld.%06" PRIi64 ", '%s', %d, %d, %d, '%s', '%s', '%s', %d )", + now_sec, static_cast(now_frac.count()), mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, + escapedString.c_str(), file, line); dbQueue.push(std::move(sql_string)); } else { puts("Db is closed"); @@ -559,6 +550,7 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con void logInit(const std::string &id, const Logger::Options &options) { if (Logger::smInstance) { delete Logger::smInstance; + Logger::smInstance = nullptr; } Logger::smInstance = new Logger(); diff --git a/src/zm_logger.h b/src/zm_logger.h index 80b6e7ba7..b437f7b55 100644 --- a/src/zm_logger.h +++ b/src/zm_logger.h @@ -23,9 +23,9 @@ #include "zm_db.h" #include "zm_config.h" #include "zm_define.h" -#include "zm_thread.h" -#include #include +#include +#include #ifdef HAVE_SYS_SYSCALL_H #include @@ -89,7 +89,7 @@ private: static bool smInitialised; static Logger *smInstance; - RecursiveMutex log_mutex; + std::recursive_mutex log_mutex; static StringMap smCodes; static IntMap smSyslogPriorities; @@ -173,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 std::string &id, const Logger::Options &options=Logger::Options()); @@ -189,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_monitor.cpp b/src/zm_monitor.cpp index 2d72f9eb6..d808ddba0 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -21,27 +21,21 @@ #include "zm_group.h" #include "zm_eventstream.h" +#include "zm_ffmpeg_camera.h" #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_remote_camera_rtsp.h" #include "zm_signal.h" #include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 #include "zm_local_camera.h" -#endif // ZM_HAS_V4L - -#if HAVE_LIBAVFORMAT -#include "zm_remote_camera_rtsp.h" -#endif // HAVE_LIBAVFORMAT - -#if HAVE_LIBAVFORMAT -#include "zm_ffmpeg_camera.h" -#endif // HAVE_LIBAVFORMAT +#endif // ZM_HAS_V4L2 #if HAVE_LIBVLC #include "zm_libvlc_camera.h" @@ -78,18 +72,21 @@ 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`, `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`, " "`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`, `RTSPServer`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`"; +"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," +"`RTSPServer`, `RTSPStreamName`," +"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { + "Unknown", "Local", "Remote", "File", @@ -97,7 +94,30 @@ std::string CameraType_Strings[] = { "LibVLC", "NVSOCKET", "CURL", - "VNC", + "VNC" +}; + +std::string Function_Strings[] = { + "Unknown", + "None", + "Monitor", + "Modect", + "Record", + "Mocord", + "Nodect" +}; + +std::string State_Strings[] = { + "Unknown", + "IDLE", + "PREALARM", + "ALARM", + "ALERT", + "TAPE" +}; + +std::string TriggerState_Strings[] = { + "Cancel", "On", "Off" }; Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) : @@ -110,7 +130,7 @@ 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); + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); #else // ZM_MEM_MAPPED shm_id = 0; #endif // ZM_MEM_MAPPED @@ -129,20 +149,21 @@ Monitor::MonitorLink::~MonitorLink() { } bool Monitor::MonitorLink::connect() { - if ( !last_connect_time || (time(nullptr) - last_connect_time) > 60 ) { - last_connect_time = time(nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); + if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) { + last_connect_time = std::chrono::system_clock::to_time_t(now); mem_size = sizeof(SharedData) + sizeof(TriggerData); - Debug(1, "link.mem.size=%d", mem_size); + Debug(1, "link.mem.size=%jd", static_cast(mem_size)); #if ZM_MEM_MAPPED - map_fd = open(mem_file, O_RDWR, (mode_t)0600); - if ( map_fd < 0 ) { - Debug(3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno)); + map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); + if (map_fd < 0) { + Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); disconnect(); return false; } - while ( map_fd <= 2 ) { + while (map_fd <= 2) { int new_map_fd = dup(map_fd); Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd); close(map_fd); @@ -150,37 +171,37 @@ bool Monitor::MonitorLink::connect() { } struct stat map_stat; - if ( fstat(map_fd, &map_stat) < 0 ) { - Error("Can't stat linked memory map file %s: %s", mem_file, strerror(errno)); + if (fstat(map_fd, &map_stat) < 0) { + Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); disconnect(); return false; } - if ( map_stat.st_size == 0 ) { - Error("Linked memory map file %s is empty: %s", mem_file, strerror(errno)); + if (map_stat.st_size == 0) { + Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno)); 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); + } else if (map_stat.st_size < mem_size) { + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(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)); + if (mem_ptr == MAP_FAILED) { + Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast(mem_size), strerror(errno)); disconnect(); return false; } #else // ZM_MEM_MAPPED shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); - if ( shm_id < 0 ) { + if (shm_id < 0) { 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; @@ -190,7 +211,7 @@ bool Monitor::MonitorLink::connect() { shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - if ( !shared_data->valid ) { + if (!shared_data->valid) { Debug(3, "Linked memory not initialised by capture daemon"); disconnect(); return false; @@ -206,62 +227,61 @@ bool Monitor::MonitorLink::connect() { } // end bool Monitor::MonitorLink::connect() bool Monitor::MonitorLink::disconnect() { - if ( connected ) { + if (connected) { connected = false; #if ZM_MEM_MAPPED - if ( mem_ptr > (void *)0 ) { - msync( mem_ptr, mem_size, MS_ASYNC ); - munmap( mem_ptr, mem_size ); + if (mem_ptr > (void *)0) { + msync(mem_ptr, mem_size, MS_ASYNC); + munmap(mem_ptr, mem_size); } - if ( map_fd >= 0 ) - close( map_fd ); + if (map_fd >= 0) + close(map_fd); map_fd = -1; #else // ZM_MEM_MAPPED struct shmid_ds shm_data; - if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { - Debug( 3, "Can't shmctl: %s", strerror(errno) ); - return( false ); + if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { + Debug(3, "Can't shmctl: %s", strerror(errno)); + return false; } 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; } last_event_id = shared_data->last_event_id; @@ -275,8 +295,8 @@ Monitor::Monitor() storage_id(0), type(LOCAL), function(NONE), - enabled(0), - decoding_enabled(0), + enabled(false), + decoding_enabled(false), //protocol //method //options @@ -299,8 +319,8 @@ Monitor::Monitor() deinterlacing_value(0), decoder_hwaccel_name(""), decoder_hwaccel_device(""), - videoRecording(0), - rtsp_describe(0), + videoRecording(false), + rtsp_describe(false), savejpegs(0), colours(0), @@ -310,13 +330,13 @@ Monitor::Monitor() encoder(""), output_container(""), imagePixFormat(AV_PIX_FMT_NONE), - subpixelorder(0), - record_audio(0), + record_audio(false), //event_prefix //label_format - label_coord(Coord(0,0)), + label_coord(Vector2(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), @@ -335,11 +355,13 @@ Monitor::Monitor() fps_report_interval(0), ref_blend_perc(0), alarm_ref_blend_perc(0), - track_motion(0), + track_motion(false), signal_check_points(0), signal_check_colour(0), - embed_exif(0), - rtsp_server(0), + embed_exif(false), + rtsp_server(false), + rtsp_streamname(""), + importance(0), capture_max_fps(0), purpose(QUERY), last_camera_bytes(0), @@ -356,21 +378,44 @@ Monitor::Monitor() 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), + 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), - n_zones(0), - zones(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) + linked_monitors(nullptr), + red_val(0), + green_val(0), + blue_val(0), + grayscale_val(0), + colour_val(0) { if ( strcmp(config.event_close_mode, "time") == 0 ) @@ -380,7 +425,7 @@ Monitor::Monitor() else event_close_mode = CLOSE_IDLE; - event = 0; + event = nullptr; last_section_mod = 0; adaptive_skip = true; @@ -393,15 +438,16 @@ Monitor::Monitor() "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, RTSPDescribe, " + "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " "SaveJPEGs, VideoWriter, EncoderParameters, - "OutputCodec, Encoder, OutputContainer," - "RecordAudio, " + "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, `RTSPServer`, SignalCheckPoints, SignalCheckColour FROM Monitors"; + "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," + "`RTSPServer`,`RTSPStreamName`, + "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { @@ -409,12 +455,11 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { int col = 0; id = atoi(dbrow[col]); col++; - strncpy(name, dbrow[col], sizeof(name)-1); col++; + name = dbrow[col]; col++; server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; storage_id = atoi(dbrow[col]); col++; - if ( storage ) - delete storage; + delete storage; storage = new Storage(storage_id); if ( ! strcmp(dbrow[col], "Local") ) { @@ -439,22 +484,26 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { 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++; + enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; + // See below after save_jpegs for a recalculation of decoding_enabled 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++; - - if ( dbrow[col] ) - strncpy(device, dbrow[col], sizeof(device)-1); - else - device[0] = 0; + analysis_update_delay = Seconds(strtoul(dbrow[col++], nullptr, 0)); + capture_delay = + (dbrow[col] && atof(dbrow[col]) > 0.0) ? std::chrono::duration_cast(FPSeconds(1 / atof(dbrow[col]))) + : Microseconds(0); + col++; + alarm_capture_delay = + (dbrow[col] && atof(dbrow[col]) > 0.0) ? std::chrono::duration_cast(FPSeconds(1 / atof(dbrow[col]))) + : Microseconds(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; @@ -475,6 +524,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { } 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++; @@ -483,96 +533,102 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { 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++; - /* Parse encoder parameters */ - ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); + decoding_enabled = !( + ( function == RECORD or function == NODECT ) + and + ( savejpegs == 0 ) + and + ( videowriter == PASSTHROUGH ) + and + !decoding_enabled + ); + Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter); + +/*"`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++; - if ( dbrow[col] ) - strncpy(event_prefix, dbrow[col], sizeof(event_prefix)-1); - else - event_prefix[0] = 0; - col++; - - if ( dbrow[col] ) - strncpy(label_format, dbrow[col], sizeof(label_format)-1); - else - label_format[0] = 0; - col++; - - // 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; - } - } - - label_coord = Coord(atoi(dbrow[col]), atoi(dbrow[col+1])); col += 2; + /* "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," */ + event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + label_format = dbrow[col] ? ReplaceAll(dbrow[col], "\\n", "\n") : ""; col++; + label_coord = Vector2(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.setMaxVideoPackets(pre_event_count); + 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; - pre_event_buffer_count = pre_event_count + alarm_frame_count + warmup_count - 1; + 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; - section_length = atoi(dbrow[col]); col++; - min_section_length = atoi(dbrow[col]); col++; + /* "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " */ + section_length = Seconds(atoi(dbrow[col])); col++; + min_section_length = Seconds(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-1 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++; + if (importance < 0) importance = 0; // Should only be >= 0 + // How many frames we need to have before we start analysing ready_count = std::max(warmup_count, pre_event_count); @@ -593,48 +649,38 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { + sizeof(TriggerData) + sizeof(VideoStoreData) //Information to pass back to the capture process + (image_buffer_count * sizeof(struct timeval)) - + (image_buffer_count * width * height * colours) + + (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, image_size, (image_buffer_count*image_size), - mem_size); - - Zone **zones = 0; - int n_zones = Zone::Load(this, zones); - this->AddZones(n_zones, zones); - this->AddPrivacyBitmask(zones); + 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); // Should maybe store this for later use - std::string monitor_dir = stringtf("%s/%d", storage->Path(), id); - LoadCamera(); + std::string monitor_dir = stringtf("%s/%u", storage->Path(), id); 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)); } - // 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); - FifoStream::fifo_create_if_missing(diag_path_ref.c_str()); - FifoStream::fifo_create_if_missing(diag_path_delta.c_str()); + 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); @@ -642,8 +688,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { } } // end if purpose - Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones); -} // Monitor::Load + 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) @@ -651,10 +697,10 @@ void Monitor::LoadCamera() { switch (type) { case LOCAL: { -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 int extras = (deinterlacing >> 24) & 0xff; - camera = ZM::make_unique(this, + camera = zm::make_unique(this, device, channel, format, @@ -680,7 +726,7 @@ void Monitor::LoadCamera() { } case REMOTE: { if (protocol == "http") { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, method, host, port, @@ -696,9 +742,8 @@ void Monitor::LoadCamera() { record_audio ); } -#if HAVE_LIBAVFORMAT else if (protocol == "rtsp") { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, method, host, // Host port, // Port @@ -715,14 +760,13 @@ void Monitor::LoadCamera() { record_audio ); } -#endif // HAVE_LIBAVFORMAT else { Error("Unexpected remote camera protocol '%s'", protocol.c_str()); } break; } case FILE: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), camera_width, camera_height, @@ -736,10 +780,10 @@ void Monitor::LoadCamera() { ); break; } -#if HAVE_LIBAVFORMAT case FFMPEG: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path, + second_path, method, options, camera_width, @@ -756,9 +800,8 @@ void Monitor::LoadCamera() { ); break; } -#endif // HAVE_LIBAVFORMAT case NVSOCKET: { - camera = ZM::make_unique(this, + camera = zm::make_unique(this, host.c_str(), port.c_str(), path.c_str(), @@ -776,7 +819,7 @@ void Monitor::LoadCamera() { } case LIBVLC: { #if HAVE_LIBVLC - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), method, options, @@ -797,7 +840,7 @@ void Monitor::LoadCamera() { } case CURL: { #if HAVE_LIBCURL - camera = ZM::make_unique(this, + camera = zm::make_unique(this, path.c_str(), user.c_str(), pass.c_str(), @@ -818,7 +861,7 @@ void Monitor::LoadCamera() { } case VNC: { #if HAVE_LIBVNC - camera = ZM::make_unique(this, + camera = zm::make_unique(this, host.c_str(), port.c_str(), user.c_str(), @@ -849,7 +892,7 @@ std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpo std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", p_id); zmDbRow dbrow; - if ( !dbrow.fetch(sql.c_str()) ) { + if (!dbrow.fetch(sql)) { Error("Can't use query result: %s", mysql_error(&dbconn)); return nullptr; } @@ -861,69 +904,73 @@ std::shared_ptr Monitor::Load(unsigned int p_id, bool load_zones, Purpo } 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.%d", staticConfig.PATH_MAP.c_str(), id); - if ( purpose != CAPTURE ) { - map_fd = open(mem_file, O_RDWR); + mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id); + if (purpose != CAPTURE) { + map_fd = open(mem_file.c_str(), O_RDWR); } else { - map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0660); + umask(0); + map_fd = open(mem_file.c_str(), O_RDWR|O_CREAT, (mode_t)0666); } - if ( map_fd < 0 ) { - Error("Can't open memory map file %s, probably not enough space free: %s", mem_file, strerror(errno)); + if (map_fd < 0) { + Error("Can't open memory map file %s: %s", mem_file.c_str(), strerror(errno)); return false; } else { - Debug(3, "Success opening mmap file at (%s)", mem_file); + Debug(3, "Success opening mmap file at (%s)", mem_file.c_str()); } 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)); + 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.c_str(), strerror(errno)); close(map_fd); map_fd = -1; return false; } - if ( map_stat.st_size != mem_size ) { - if ( purpose == CAPTURE ) { + 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 %d bytes: %s", mem_file, mem_size, strerror(errno)); + if (ftruncate(map_fd, mem_size) < 0) { + Error("Can't extend memory map file %s to %jd bytes: %s", mem_file.c_str(), 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, mem_size); + } 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 %d", map_stat.st_size, mem_size); + Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast(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 (%d bytes) to locked memory, trying unlocked", mem_file, mem_size); + if (mem_ptr == MAP_FAILED) { + if (errno == EAGAIN) { + Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), static_cast(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); + Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), static_cast(mem_size)); #ifdef MAP_LOCKED } else { - Error("Unable to map file %s (%d bytes) to locked memory (%s)", mem_file, mem_size, strerror(errno)); + Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), static_cast(mem_size), strerror(errno)); } } #endif - if ( (mem_ptr == MAP_FAILED) or (mem_ptr == nullptr) ) { - Error("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno); + if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) { + Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), static_cast(mem_size), strerror(errno), errno); close(map_fd); map_fd = -1; mem_ptr = nullptr; @@ -931,11 +978,11 @@ bool Monitor::connect() { } #else // ZM_MEM_MAPPED shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700); - if ( shm_id < 0 ) { + 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 ( mem_ptr < (void *)0 ) { + if ((int)mem_ptr == -1) { Fatal("Can't shmat: %s", strerror(errno)); } #endif // ZM_MEM_MAPPED @@ -946,36 +993,28 @@ bool Monitor::connect() { 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 ) { + 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(); + 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 ( deinterlacing_value == 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()); + image_buffer.resize(image_buffer_count); + for (int32_t i = 0; i < image_buffer_count; i++) { + image_buffer[i] = new Image(width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()])); + image_buffer[i]->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } + Debug(3, "Allocated %zu %zu image buffers", image_buffer.capacity(), image_buffer.size()); - if ( purpose == CAPTURE ) { + 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->state = state = IDLE; shared_data->last_write_index = image_buffer_count; shared_data->last_read_index = image_buffer_count; shared_data->last_write_time = 0; @@ -990,29 +1029,31 @@ bool Monitor::connect() { 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 = 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 = {}; // 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); - } else if ( !shared_data->valid ) { - Error("Shared data not initialised by capture daemon for monitor %s", name); + usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal + 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; } // 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; + last_fps_time = std::chrono::system_clock::now(); + last_analysis_fps_time = std::chrono::system_clock::now(); Debug(3, "Success connecting"); return true; @@ -1024,6 +1065,13 @@ bool Monitor::disconnect() { return true; } + if (purpose == CAPTURE) { + if (unlink(mem_file.c_str()) < 0) { + Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno)); + } + Debug(1, "Setting shared_data->valid = false"); + shared_data->valid = false; + } #if ZM_MEM_MAPPED msync(mem_ptr, mem_size, MS_ASYNC); munmap(mem_ptr, mem_size); @@ -1033,9 +1081,6 @@ bool Monitor::disconnect() { mem_ptr = nullptr; shared_data = nullptr; - if (purpose == CAPTURE and (unlink(mem_file) < 0) ) { - Warning("Can't unlink '%s': %s", mem_file, strerror(errno)); - } #else // ZM_MEM_MAPPED struct shmid_ds shm_data; if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { @@ -1056,16 +1101,10 @@ bool Monitor::disconnect() { } #endif // ZM_MEM_MAPPED - 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; + 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_buffer[i] = nullptr; } return true; @@ -1076,28 +1115,14 @@ Monitor::~Monitor() { 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); - if ( (deinterlacing & 0xff) == 4 ) { - delete next_buffer.image; - delete next_buffer.timestamp; - } } // end if purpose != query disconnect(); } // end if mem_ptr - if (analysis_it) { - packetqueue.free_it(analysis_it); - analysis_it = nullptr; - } - - for ( int i = 0; i < n_zones; i++ ) { - delete zones[i]; - } - delete[] zones; + // Will be free by packetqueue destructor + analysis_it = nullptr; + decoder_it = nullptr; delete storage; if (n_linked_monitors) { @@ -1107,86 +1132,97 @@ Monitor::~Monitor() { delete[] linked_monitors; linked_monitors = 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; + } } // end Monitor::~Monitor() -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 ) { + 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(); } int Monitor::GetImage(int32_t index, int scale) { - if ( index < 0 || index > image_buffer_count ) { + if (index < 0 || index > image_buffer_count) { + Debug(1, "Invalid index %d passed. image_buffer_count = %d", 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 ) ) { - ZMPacket *snap = &image_buffer[index]; - alarm_image.Assign(*snap->image); + if (!image_buffer.size() or static_cast(index) >= image_buffer.size()) { + Error("Image Buffer has not been allocated"); + return -1; + } + if ( index == image_buffer_count ) { + Error("Unable to generate image, no images in buffer"); + return 0; + } - if ( scale != ZM_SCALE_BASE ) { - alarm_image.Scale(scale); - } + 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)) { + alarm_image.Assign(*image_buffer[index]); - if ( !config.timestamp_on_capture ) { - TimestampImage(&alarm_image, snap->timestamp); - } - image = &alarm_image; - } else { - image = image_buffer[index].image; + if (scale != ZM_SCALE_BASE) { + alarm_image.Scale(scale); } - static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Monitor%d.jpg", id); - image->WriteJpeg(filename); + if (!config.timestamp_on_capture) { + TimestampImage(&alarm_image, SystemTimePoint(zm::chrono::duration_cast(shared_timestamps[index]))); + } + image = &alarm_image; } else { - Error("Unable to generate image, no images in buffer"); + image = image_buffer[index]; } - return 0; + + std::string filename = stringtf("Monitor%u.jpg", id); + image->WriteJpeg(filename); + return 1; } ZMPacket *Monitor::getSnapshot(int index) const { - - if ( (index < 0) || (index > image_buffer_count) ) { + if ((index < 0) || (index >= image_buffer_count)) { index = shared_data->last_write_index; } - return &image_buffer[index]; - + if (!image_buffer.size() or static_cast(index) >= image_buffer.size()) { + Error("Image Buffer has not been allocated"); + return nullptr; + } + if (index != image_buffer_count) { + return new ZMPacket(image_buffer[index], + SystemTimePoint(zm::chrono::duration_cast(shared_timestamps[index]))); + } else { + Error("Unable to generate image, no images in buffer"); + } return nullptr; } -struct timeval Monitor::GetTimestamp(int index) const { +SystemTimePoint Monitor::GetTimestamp(int index) const { ZMPacket *packet = getSnapshot(index); - if ( packet ) - return *packet->timestamp; + if (packet) + return packet->timestamp; - static struct timeval null_tv = { 0, 0 }; - return null_tv; + return {}; } unsigned int Monitor::GetLastReadIndex() const { @@ -1204,69 +1240,14 @@ uint64_t Monitor::GetLastEventId() const { // 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; - 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; - } - 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; - - 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->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 ]; - fps_image_count--; - } - struct timeval time2 = *snap2->timestamp; - - double time_diff = tvDiffSec( time2, time1 ); - if ( ! time_diff ) { - Error("No diff between time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", - 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 = 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", - curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); - return 0.0; - } else { - Debug(2, "GetFPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", - curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count); - } - 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() { - Debug(1, "Here"); double capture_fps = get_capture_fps(); - Debug(1, "Here"); if ( !analysis_fps_limit ) { return 0; } else if ( analysis_fps_limit > capture_fps ) { - Debug(1, "Here"); 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); @@ -1316,16 +1297,14 @@ void Monitor::actionReload() { void Monitor::actionEnable() { shared_data->action |= RELOAD; - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %d", id); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 1 WHERE `Id` = %u", id); zmDbDo(sql); } void Monitor::actionDisable() { shared_data->action |= RELOAD; - char sql[ZM_SQL_SML_BUFSIZ]; - snprintf(sql, sizeof(sql), "UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %d", id); + std::string sql = stringtf("UPDATE `Monitors` SET `Enabled` = 0 WHERE `Id` = %u", id); zmDbDo(sql); } @@ -1338,127 +1317,153 @@ void Monitor::actionResume() { } int Monitor::actionBrightness(int p_brightness) { - if ( purpose != CAPTURE ) { - if ( p_brightness >= 0 ) { - shared_data->brightness = p_brightness; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to set brightness"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to get brightness"); - return -1; - } - } - } - return shared_data->brightness; + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Brightness(p_brightness); } - return camera->Brightness(p_brightness); -} // end int Monitor::actionBrightness(int p_brightness) + + // If we are an outside actor, sending the command + shared_data->brightness = p_brightness; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set brightness"); + return -1; + } + } + return shared_data->brightness; +} + +int Monitor::actionBrightness() { + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Brightness(); + } + + // If we are an outside actor, sending the command + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get brightness"); + return -1; + } + } + return shared_data->brightness; +} // end int Monitor::actionBrightness() int Monitor::actionContrast(int p_contrast) { - if ( purpose != CAPTURE ) { - if ( p_contrast >= 0 ) { - shared_data->contrast = p_contrast; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to set contrast"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to get contrast"); - return -1; - } - } - } - return shared_data->contrast; + if (purpose == CAPTURE) { + return camera->Contrast(p_contrast); } - return camera->Contrast(p_contrast); -} // end int Monitor::actionContrast(int p_contrast) + + shared_data->contrast = p_contrast; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set contrast"); + return -1; + } + } + return shared_data->contrast; +} + +int Monitor::actionContrast() { + if (purpose == CAPTURE) { + // We are the capture process, so take the action + return camera->Contrast(); + } + + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get contrast"); + return -1; + } + } + return shared_data->contrast; +} // end int Monitor::actionContrast() int Monitor::actionHue(int p_hue) { - if ( purpose != CAPTURE ) { - if ( p_hue >= 0 ) { - shared_data->hue = p_hue; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to set hue"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to get hue"); - return -1; - } - } - } - return shared_data->hue; + if (purpose == CAPTURE) { + return camera->Hue(p_hue); } - return camera->Hue(p_hue); + + shared_data->hue = p_hue; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set hue"); + return -1; + } + } + return shared_data->hue; +} + +int Monitor::actionHue() { + if (purpose == CAPTURE) { + return camera->Hue(); + } + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get hue"); + return -1; + } + } + return shared_data->hue; } // end int Monitor::actionHue(int p_hue) int Monitor::actionColour(int p_colour) { - if ( purpose != CAPTURE ) { - if ( p_colour >= 0 ) { - shared_data->colour = p_colour; - shared_data->action |= SET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to set colour"); - return -1; - } - } - } else { - shared_data->action |= GET_SETTINGS; - int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) { - if ( wait_loops-- ) { - usleep(100000); - } else { - Warning("Timed out waiting to get colour"); - return -1; - } - } - } - return shared_data->colour; + if (purpose == CAPTURE) { + return camera->Colour(p_colour); } - return camera->Colour(p_colour); + shared_data->colour = p_colour; + shared_data->action |= SET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & SET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to set colour"); + return -1; + } + } + return shared_data->colour; +} + +int Monitor::actionColour() { + if (purpose == CAPTURE) { + return camera->Colour(); + } + shared_data->action |= GET_SETTINGS; + int wait_loops = 10; + while (shared_data->action & GET_SETTINGS) { + if (wait_loops--) { + std::this_thread::sleep_for(Milliseconds(100)); + } else { + Warning("Timed out waiting to get colour"); + return -1; + } + } + return shared_data->colour; } // end int Monitor::actionColour(int p_colour) void Monitor::DumpZoneImage(const char *zone_string) { @@ -1483,7 +1488,7 @@ void Monitor::DumpZoneImage(const char *zone_string) { // Grab the most revent event image std::string sql = stringtf("SELECT MAX(`Id`) FROM `Events` WHERE `MonitorId`=%d AND `Frames` > 0", id); zmDbRow eventid_row; - if ( eventid_row.fetch(sql.c_str()) ) { + if (eventid_row.fetch(sql)) { uint64_t event_id = atoll(eventid_row[0]); Debug(3, "Got event %" PRIu64, event_id); @@ -1502,127 +1507,117 @@ 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 ) + extra_zone.Clip(Box( + {0, 0}, + {static_cast(zone_image->Width()), static_cast(zone_image->Height())} + )); + + for (const Zone &zone : zones) { + if (exclude_id && (!extra_colour || !extra_zone.GetVertices().empty()) && zone.Id() == exclude_id) { continue; + } Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) { + if (exclude_id && extra_zone.GetVertices().empty() && zone.Id() == exclude_id) { colour = extra_colour; } else { - if ( zones[i]->IsActive() ) { + if (zone.IsActive()) { colour = kRGBRed; - } else if ( zones[i]->IsInclusive() ) { + } else if (zone.IsInclusive()) { colour = kRGBOrange; - } else if ( zones[i]->IsExclusive() ) { + } else if (zone.IsExclusive()) { colour = kRGBPurple; - } else if ( zones[i]->IsPreclusive() ) { + } else if (zone.IsPreclusive()) { colour = kRGBBlue; } else { 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() ) { + if (!extra_zone.GetVertices().empty()) { zone_image->Fill(extra_colour, 2, extra_zone); zone_image->Outline(extra_colour, extra_zone); } - static char filename[PATH_MAX]; - snprintf(filename, sizeof(filename), "Zones%d.jpg", id); + std::string filename = stringtf("Zones%u.jpg", id); zone_image->WriteJpeg(filename); delete zone_image; } // end void Monitor::DumpZoneImage(const char *zone_string) 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); - if ( dump_image->WriteJpeg(new_filename) ) - rename(new_filename, filename); + if (image_count && !(image_count % 10)) { + + std::string filename = stringtf("Monitor%u.jpg", id); + std::string new_filename = stringtf("Monitor%u-new.jpg", id); + + if (dump_image->WriteJpeg(new_filename)) { + rename(new_filename.c_str(), filename.c_str()); + } } } // 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) void Monitor::CheckAction() { - struct timeval now; - gettimeofday(&now, nullptr); + SystemTimePoint now = std::chrono::system_clock::now(); if ( shared_data->action ) { // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? @@ -1639,124 +1634,76 @@ void Monitor::CheckAction() { } else { Info("Received suspend indication at count %d, but wasn't active", image_count); } - if ( config.max_suspend_time ) { - auto_resume_time = now.tv_sec + config.max_suspend_time; + if (config.max_suspend_time) { + auto_resume_time = now + Seconds(config.max_suspend_time); } shared_data->action &= ~SUSPEND; } else if ( shared_data->action & RESUME ) { if ( Enabled() && !Active() ) { Info("Received resume indication at count %d", image_count); shared_data->active = true; - ref_image = *(image_buffer[shared_data->last_write_index].image); - ready_count = std::max(warmup_count, pre_event_count); + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread shared_data->alarm_x = shared_data->alarm_y = -1; } shared_data->action &= ~RESUME; } } // end if shared_data->action - if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { + if (auto_resume_time.time_since_epoch() != Seconds(0) && now >= auto_resume_time) { Info("Auto resuming at count %d", image_count); shared_data->active = true; - ref_image.Assign(*(image_buffer[shared_data->last_write_index].image)); - ready_count = std::max(warmup_count, pre_event_count); - auto_resume_time = 0; + ref_image.DumpImgBuffer(); // Will get re-assigned by analysis thread } } -void Monitor::UpdateCaptureFPS() { - if ( fps_report_interval and - ( +void Monitor::UpdateFPS() { + if ( fps_report_interval and + ( !(image_count%fps_report_interval) - or + 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; + SystemTimePoint now = std::chrono::system_clock::now(); + FPSeconds elapsed = now - last_fps_time; // 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 ) { + if (elapsed > Seconds(1)) { // # 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; + double new_capture_fps = (image_count - last_capture_image_count) / elapsed.count(); + uint32 new_camera_bytes = camera->Bytes(); + uint32 new_capture_bandwidth = + static_cast((new_camera_bytes - last_camera_bytes) / elapsed.count()); + double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count(); + + Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps", + image_count, + last_capture_image_count, + image_count - last_capture_image_count, + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_fps_time.time_since_epoch()).count(), + elapsed.count(), + new_capture_fps, + new_analysis_fps); + + Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec Analysing at %.2lf fps", + name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps); + + shared_data->capture_fps = new_capture_fps; + last_fps_time = now; + last_capture_image_count = image_count; + shared_data->analysis_fps = new_analysis_fps; + last_motion_frame_count = motion_frame_count; last_camera_bytes = new_camera_bytes; - 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: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", - name, 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; - - 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, new_capture_fps, new_capture_bandwidth, new_capture_fps, new_capture_bandwidth); - dbQueue.push(sql); + std::string sql = stringtf( + "UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u", + new_capture_fps, new_capture_bandwidth, new_analysis_fps, 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:%d.%d = %lf, last %lf, diff %lf", name, analysis_image_count, - now.tv_sec, 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, 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; - - 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, new_analysis_fps, new_analysis_fps); - dbQueue.push(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 +} // void Monitor::UpdateFPS() // Would be nice if this JUST did analysis // This idea is that we should be analysing as close to the capture frame as possible. @@ -1765,75 +1712,66 @@ void Monitor::UpdateAnalysisFPS() { // 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 ( !Enabled() ) { - Warning("Shouldn't be doing Analyse when not Enabled"); - return false; - } - if ( !analysis_it ) - analysis_it = packetqueue.get_video_it(true); - // 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 - ZMPacket *snap = packetqueue.get_packet(analysis_it); - if ( !snap ) return false; + ZMLockedPacket *packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + std::shared_ptr snap = packet_lock->packet_; // Is it possible for snap->score to be ! -1 ? Not if everything is working correctly - if ( snap->score != -1 ) { - snap->unlock(); - packetqueue.increment_it(analysis_it); + if (snap->score != -1) { Error("skipping because score was %d", snap->score); + packetqueue.unlock(packet_lock); + packetqueue.increment_it(analysis_it); 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(%d) image index %d", - signal, signal_change, int(trigger_data->trigger_state), snap->image_index); + 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 ) { + 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() ) { + if (!Ready()) { Debug(3, "Not ready?"); - snap->unlock(); + delete packet_lock; return false; } - Debug(4, "Ready"); 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 ) { + 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 ) { + 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 + } // end if trigger_on - if ( signal_change ) { + // 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 ) { + if (!signal) { signalText = "Lost"; - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name, analysis_image_count, event->Id()); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", signal loss", name.c_str(), analysis_image_count, event->Id()); closeEvent(); last_section_mod = 0; } @@ -1841,9 +1779,8 @@ bool Monitor::Analyse() { signalText = "Reacquired"; score += 100; } - if ( !event ) { - if ( cause.length() ) - cause += ", "; + if (!event) { + if (cause.length()) cause += ", "; cause += SIGNAL_CAUSE; } Event::StringSet noteSet; @@ -1851,69 +1788,29 @@ bool Monitor::Analyse() { noteSetMap[SIGNAL_CAUSE] = noteSet; shared_data->state = state = IDLE; shared_data->active = signal; - if ( (function == MODECT or function == MOCORD) and snap->image ) + if ((function == MODECT or function == MOCORD) and snap->image) ref_image.Assign(*(snap->image)); - }// else - - if ( signal ) { - if ( snap->image or (snap->packet.stream_index == video_stream_id) ) { - struct timeval *timestamp = snap->timestamp; - - if ( Active() and (function == MODECT or function == MOCORD) and snap->image ) { - Debug(3, "signal and active and modect"); - Event::StringSet zoneSet; - - int motion_score = last_motion_score; - - 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 ) { - // Get new score. - motion_score = DetectMotion(*(snap->image), zoneSet); - - Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, motion_score); - } else { - Warning("No image in snap"); - } - // Why are we updating the last_motion_score too? - last_motion_score = motion_score; - motion_frame_count += 1; - } else { - Debug(1, "Skipped motion detection"); - } - if ( motion_score ) { - score += motion_score; - if ( cause.length() ) - cause += ", "; - cause += MOTION_CAUSE; - noteSetMap[MOTION_CAUSE] = zoneSet; - } // end if motion_score - } // end if active and doing motion detection + } // end if signal change + if (signal) { + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { // Check to see if linked monitors are triggering. - if ( n_linked_monitors > 0 ) { - Debug(4, "Checking linked monitors"); + if (n_linked_monitors > 0) { + Debug(1, "Checking linked monitors"); // FIXME improve logic here bool first_link = true; Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) { + for (int i = 0; i < n_linked_monitors; i++) { // TODO: Shouldn't we try to connect? - if ( linked_monitors[i]->isConnected() ) { - Debug(4, "Linked monitor %d %s is connected", + 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(4, "Linked monitor %d %s is alarmed", + 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() ) + if (!event) { + if (first_link) { + if (cause.length()) cause += ", "; cause += LINKED_CAUSE; first_link = false; @@ -1922,7 +1819,7 @@ bool Monitor::Analyse() { noteSet.insert(linked_monitors[i]->Name()); score += linked_monitors[i]->lastFrameScore(); // 50; } else { - Debug(4, "Linked monitor %d %s is not alarmed", + Debug(1, "Linked monitor %d %s is not alarmed", linked_monitors[i]->Id(), linked_monitors[i]->Name()); } } else { @@ -1930,269 +1827,398 @@ bool Monitor::Analyse() { linked_monitors[i]->connect(); } } // end foreach linked_monitor - if ( noteSet.size() > 0 ) + if (noteSet.size() > 0) noteSetMap[LINKED_CAUSE] = noteSet; } // end if linked_monitors - if ( function == RECORD || function == MOCORD ) { + /* try to stay behind the decoder. */ + if (decoding_enabled) { + while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { + // Need to wait for the decoder thread. + Debug(1, "Waiting for decode"); + packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all + packetqueue.wait(); + + // Another thread may have moved our it. Unlikely but possible + packet_lock = packetqueue.get_packet(analysis_it); + if (!packet_lock) return false; + snap = packet_lock->packet_; + + 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 + + SystemTimePoint timestamp = snap->timestamp; + + if (Active() and (function == MODECT or function == MOCORD)) { + Debug(3, "signal and active and modect"); + Event::StringSet zoneSet; + + 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. + int 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; + last_motion_score = motion_score; + if (motion_score) { + if (cause.length()) cause += ", "; + cause += MOTION_CAUSE; + noteSetMap[MOTION_CAUSE] = zoneSet; + } // end if motion_score + } + } else { + Debug(1, "no image so skipping motion detection"); + } // end if has image + } else { + Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); + } + score += last_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 (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 record", event->Id()); - 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)) || ! ( 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 - ); + if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length) + && ((function == MOCORD && event_close_mode != CLOSE_TIME) + || (function == RECORD && event_close_mode == CLOSE_TIME) + || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { + Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , + name.c_str(), + image_count, + event->Id(), + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(Seconds(section_length).count())); closeEvent(); - } // end if section_length - } // end if event + } // end if section_length + } // end if event - if ( !event ) { + if (!event) { Debug(2, "Creating continuous event"); - if ( !snap->keyframe and (videowriter == PASSTHROUGH) ) { + 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 */ + *analysis_it, 0 /* pre_event_count */ ); // This gets a lock on the starting packet - ZMPacket *starting_packet = packetqueue.get_packet(start_it); - event = new Event(this, *(starting_packet->timestamp), "Continuous", noteSetMap); + ZMLockedPacket *starting_packet_lock = nullptr; + std::shared_ptr starting_packet = nullptr; + if (*start_it != *analysis_it) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) { + Warning("Unable to get starting packet lock"); + delete 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 ) { + while (starting_packet and ((*start_it) != *analysis_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 ) { - starting_packet->unlock(); + if ((*start_it) == *analysis_it) { + if (starting_packet_lock) delete starting_packet_lock; break; } - ZMPacket *p = packetqueue.get_packet(start_it); - starting_packet->unlock(); - starting_packet = p; + 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); + 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 = "Continuous"; - for ( int i=0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - alarm_cause += std::string(zones[i]->Label()); - if ( i < n_zones-1 ) { - alarm_cause += ","; - } + 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+" "+alarm_cause; + 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(); + SetVideoWriterStartTime(event->StartTime()); + Info("%s: %03d - Opened new event %" PRIu64 ", section start", - name, analysis_image_count, event->Id()); + name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) { + if (state == IDLE) { shared_data->state = state = TAPE; } } // end if ! event } // end if RECORDING - if ( score ) { - if ( (state == IDLE) || (state == TAPE) || (state == PREALARM) ) { + if (score and (function != MONITOR)) { + 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() - && (!event->AlarmFrames()) + 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) ) - ) { + && ((timestamp - event->StartTime()) >= min_section_length) + && ((!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 ) { + } 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 - ); + Debug(3, + "pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d", + Event::PreAlarmCount(), pre_event_count, + event->Frames(), + event->AlarmFrames(), + static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(Seconds(min_section_length).count()), + (event_close_mode == CLOSE_ALARM)); } - if ( (!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1) ) { + 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()); + 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 ) { + if (!event) { packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - snap_it, + *analysis_it, (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) ); - ZMPacket *starting_packet = *(*start_it); + ZMLockedPacket *starting_packet_lock = nullptr; + std::shared_ptr starting_packet = nullptr; + if (*start_it != *analysis_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, cause, noteSetMap); + shared_data->last_event_id = event->Id(); + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + SetVideoWriterStartTime(event->StartTime()); + shared_data->state = state = ALARM; - event = new Event(this, *(starting_packet->timestamp), cause, noteSetMap); // Write out starting packets, do not modify packetqueue it will garbage collect itself - while ( *start_it != snap_it ) { + while (*start_it != *analysis_it) { event->AddPacket(starting_packet); packetqueue.increment_it(start_it); - if ( (*start_it) == snap_it ) { - starting_packet->unlock(); + if ( (*start_it) == (*analysis_it) ) { + if (starting_packet_lock) delete starting_packet_lock; break; } - ZMPacket *p = packetqueue.get_packet(start_it); - starting_packet->unlock(); - starting_packet = p; + 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_; } packetqueue.free_it(start_it); delete start_it; start_it = nullptr; - 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(); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } else { shared_data->state = state = ALARM; - - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name, image_count, event->Id()); } // 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, analysis_image_count); + } 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 ) { + } 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, analysis_image_count,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, analysis_image_count); + 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 = analysis_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, analysis_image_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 ) { - if ( - ( analysis_image_count-last_alarm_count > post_event_count ) + } else if (state == ALERT) { + if ( + ((analysis_image_count - last_alarm_count) > post_event_count) && - ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= min_section_length ) - ) { + ((timestamp - event->StartTime()) >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", - name, analysis_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 != RECORD && function != MOCORD ) || event_close_mode == CLOSE_ALARM ) { shared_data->state = state = IDLE; Info("%s: %03d - Closing event %" PRIu64 ", alarm end%s", - name, analysis_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; } } - } else if ( state == PREALARM ) { + } else if (state == PREALARM) { // Back to IDLE shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE); } else { - Debug(1, "State %d beacuse image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%d) - recording.tv_src(%d) >= min_section_length(%d)", - state, analysis_image_count, last_alarm_count, post_event_count, - timestamp->tv_sec, video_store_data->recording.tv_sec, min_section_length); + Debug(1, + "State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")", + state, + State_Strings[state].c_str(), + analysis_image_count, + last_alarm_count, + post_event_count, + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(Seconds(min_section_length).count())); } - if ( Event::PreAlarmCount() ) + if (Event::PreAlarmCount()) Event::EmptyPreAlarmFrames(); } // end if score or not snap->score = score; - if ( state == PREALARM ) { + if (state == PREALARM) { // Generate analysis images if necessary - if ( (savejpegs > 1) and snap->image ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - if ( ! snap->analysis_image ) + if ((savejpegs > 1) and snap->image) { + for (const Zone &zone : zones) { + if (zone.Alarmed() and zone.AlarmImage()) { + if (!snap->analysis_image) snap->analysis_image = new Image(*(snap->image)); - snap->analysis_image->Overlay(*(zones[i]->AlarmImage())); - } + 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 ) { - if ( ( savejpegs > 1 ) and snap->image ) { - for ( int i = 0; i < n_zones; i++ ) { - if ( zones[i]->Alarmed() ) { - if ( zones[i]->AlarmImage() ) { - if ( ! snap->analysis_image ) - snap->analysis_image = new Image(*(snap->image)); - snap->analysis_image->Overlay(*(zones[i]->AlarmImage())); - } - if ( config.record_event_stats ) - zones[i]->RecordStats(event); - } // end if zone is alarmed - } // end foreach zone - } - 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_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(); + 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 != Seconds(0) && (timestamp - event->StartTime() >= section_length)) { + Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, + name.c_str(), analysis_image_count, event->Id(), + static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(Seconds(section_length).count())); + 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()); + SetVideoWriterStartTime(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 ) + if ((noteSetMap.size() > 0) and event) 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 ) { - ref_image.Blend(*(snap->image), ( state==ALARM ? alarm_ref_blend_perc : ref_blend_perc )); + 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 @@ -2200,77 +2226,72 @@ bool Monitor::Analyse() { shared_data->last_frame_score = score; } else { Debug(3, "trigger == off"); - if ( event ) { - Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name, analysis_image_count, event->Id()); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name.c_str(), analysis_image_count, event->Id()); closeEvent(); } shared_data->state = state = IDLE; - trigger_data->trigger_state = TriggerState::TRIGGER_CANCEL; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if ( event ) event->AddPacket(snap); + if (event) event->AddPacket(snap); - if ( videowriter == PASSTHROUGH and ! savejpegs ) { + // 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; } - // popPacket will have placed a second lock on snap, so release it here. - snap->unlock(); - shared_data->last_read_index = snap->image_index; - shared_data->last_read_time = time(nullptr); - analysis_image_count++; - UpdateAnalysisFPS(); packetqueue.clearPackets(snap); + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Only do these if it's a video packet. + shared_data->last_read_index = snap->image_index; + analysis_image_count++; + } + packetqueue.increment_it(analysis_it); + packetqueue.unlock(packet_lock); + shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + return true; } // end Monitor::Analyse void Monitor::Reload() { - Debug(1, "Reloading monitor %s", name); + Debug(1, "Reloading monitor %s", name.c_str()); // 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, image_count, event->Id()); + if (event) { + Info("%s: %03d - Closing event %" PRIu64 ", reloading", name.c_str(), image_count, event->Id()); closeEvent(); } std::string sql = load_monitor_sql + stringtf(" WHERE Id=%d", id); - zmDbRow *row = zmDbFetchOne(sql.c_str()); - if ( !row ) { + zmDbRow *row = zmDbFetchOne(sql); + if (!row) { Error("Can't run query: %s", mysql_error(&dbconn)); - } else if ( MYSQL_ROW dbrow = row->mysql_row() ) { - Load(dbrow, 1, purpose); - - shared_data->state = state = IDLE; - shared_data->alarm_x = shared_data->alarm_y = -1; - if ( enabled ) - shared_data->active = true; - ready_count = std::max(warmup_count, pre_event_count); + } else if (MYSQL_ROW dbrow = row->mysql_row()) { + Load(dbrow, true /*load zones */, purpose); delete 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++ ) { delete linked_monitors[i]; @@ -2291,7 +2312,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { while ( 1 ) { dest_ptr = link_id_str; while ( *src_ptr >= '0' && *src_ptr <= '9' ) { - if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { + if ( (unsigned int)(dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { *dest_ptr++ = *src_ptr++; } else { break; @@ -2328,25 +2349,19 @@ 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]); - std::lock_guard lck(db_mutex); - 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)) { - Error("Can't run query: %s", mysql_error(&dbconn)); + + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { continue; } - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - continue; - } int n_monitors = mysql_num_rows(result); if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -2362,10 +2377,11 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) { } // end if p_linked_monitors } // end void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) -std::vector> Monitor::LoadMonitors(std::string sql, Purpose purpose) { +std::vector> Monitor::LoadMonitors(const 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()); + MYSQL_RES *result = zmDbFetch(sql); if (!result) { Error("Can't load local monitors: %s", mysql_error(&dbconn)); return {}; @@ -2391,225 +2407,137 @@ std::vector> Monitor::LoadMonitors(std::string sql, Pur return monitors; } -#if ZM_HAS_V4L -std::vector> Monitor::LoadLocalMonitors(const char *device, Purpose purpose) { +#if ZM_HAS_V4L2 +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, 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 +#endif // ZM_HAS_V4L2 -std::vector> Monitor::LoadRemoteMonitors(const char *protocol, const char *host, const char *port, const char *path, 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); - - if ( protocol ) - sql += stringtf(" AND `Protocol` = '%s' AND `Host` = '%s' AND `Port` = '%s' AND `Path` = '%s'", protocol, host, port, path); - return LoadMonitors(sql, purpose); +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); } std::vector> Monitor::LoadFileMonitors(const char *file, 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, 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 std::vector> Monitor::LoadFfmpegMonitors(const char *file, 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, 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 /* 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 - unsigned int index = image_count % image_buffer_count; + if (image_buffer.empty() or (index >= image_buffer.size())) { + Error("Image Buffer is invalid. Check ImageBufferCount. size is %zu", image_buffer.size()); + return -1; + } - ZMPacket *packet = new ZMPacket(); - packet->timestamp = new struct timeval; + std::shared_ptr packet = std::make_shared(); packet->image_index = image_count; - gettimeofday(packet->timestamp, nullptr); + packet->timestamp = std::chrono::system_clock::now(); + shared_data->zmc_heartbeat_time = std::chrono::system_clock::to_time_t(packet->timestamp); + int captureResult = camera->Capture(packet); + Debug(4, "Back from capture result=%d image count %d", captureResult, image_count); - Image* capture_image = image_buffer[index].image; - int captureResult = 0; - - 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 */ - captureResult = camera->Capture(*packet); - // How about set shared_data->current_timestamp - gettimeofday(packet->timestamp, nullptr); - - if ( FirstCapture ) { - FirstCapture = 0; - return 0; - } - } else { - captureResult = camera->Capture(*packet); - Debug(4, "Back from capture result=%d image %d", captureResult, image_count); - - 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; - /* HTML colour code is actually BGR in memory, we want RGB */ - signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); - capture_image = new Image(width, height, camera->Colours(), camera->SubpixelOrder()); - capture_image->Fill(signalcolor); - shared_data->signal = false; + if (captureResult < 0) { + // 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; + /* 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); + shared_data->signal = false; + shared_data->last_write_index = index; + shared_data->last_write_time = shared_timestamps[index].tv_sec; + image_buffer[index]->Assign(*capture_image); + shared_timestamps[index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); + delete capture_image; + image_count++; + // 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 = 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 ) { - Debug(2, "Have packet stream_index:%d ?= videostream_id:(%d) q.vpktcount(%d) event?(%d) ", - packet->packet.stream_index, video_stream_id, packetqueue.packet_count(video_stream_id), ( event ? 1 : 0 ) ); + shared_data->last_write_time = std::chrono::system_clock::to_time_t(packet->timestamp); + } + 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->packet.stream_index != video_stream_id) and ! packet->image ) { - // 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"); - packetqueue.queuePacket(packet); - } else { - Debug(4, "Not Queueing audio packet"); - delete packet; + 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 + AVStream *stream = camera->getVideoStream(); + video_fifo->write( + static_cast(stream->codecpar->extradata), + stream->codecpar->extradata_size, + packet->pts); } - // 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; - } // end if audio - - if ( !packet->image ) { - if ( packet->packet.size and !packet->in_frame ) { - if ( !decoding_enabled ) { - Debug(1, "Not decoding"); - } else { - Debug(2,"About to decode %p", packet); - // 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->get_VideoCodecContext()); - if ( ret < 0 ) { - Error("decode failed"); - } else if ( ret == 0 ) { - delete packet; - return 0; - } // end if decode - } // end if decoding - } else { - Debug(1, "No packet.size(%d) or packet->in_frame(%p). Not decoding", packet->packet.size, packet->in_frame); - } - if ( packet->in_frame and !packet->image ) { - capture_image = packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder()); - packet->get_image(); - } - } // end if need to decode - - if ( packet->image ) { - capture_image = packet->image; - - /* Deinterlacing */ - if ( deinterlacing_value ) { - 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 ) { - 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(1, "Applying privacy"); - capture_image->MaskPrivacy(privacy_bitmask); - } - - if ( config.timestamp_on_capture ) { - Debug(1, "Timestampprivacy"); - TimestampImage(packet->image, packet->timestamp); - } - - if ( !ref_image.Buffer() ) { - // First image, so assign it to ref image - Debug(1, "Assigning ref image %dx%d size: %d", width, height, camera->ImageSize()); - ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(), - packet->image->Buffer(), camera->ImageSize()); - } - image_buffer[index].image->Assign(*(packet->image)); - *(image_buffer[index].timestamp) = *(packet->timestamp); - } // end if have image - - 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; - image_count++; - - // Will only be queued if there are iterators allocated in the queue. - if ( !packetqueue.queuePacket(packet) ) { - delete packet; + video_fifo->writePacket(*packet); } - UpdateCaptureFPS(); - } else { // result == 0 - // Question is, do we update last_write_index etc? - delete packet; - return 0; - } // end if result - } // end if deinterlacing + } 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"); + } + // 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); + return 1; + } // end if audio + + image_count++; + + // Will only be queued if there are iterators allocated in the queue. + packetqueue.queuePacket(packet); + } else { // result == 0 + // Question is, do we update last_write_index etc? + 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 ) { @@ -2629,22 +2557,198 @@ int Monitor::Capture() { return captureResult; } // end Monitor::Capture -void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) const { - if ( !label_format[0] ) +bool Monitor::Decode() { + ZMLockedPacket *packet_lock = packetqueue.get_packet_and_increment_it(decoder_it); + if (!packet_lock) return false; + std::shared_ptr packet = packet_lock->packet_; + 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 and !zm_terminate) { + 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()); + AVPixelFormat inputPixFormat; + bool changeColorspaceDetails = false; + switch (input_frame->format) { + case AV_PIX_FMT_YUVJ420P: + inputPixFormat = AV_PIX_FMT_YUV420P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ422P: + inputPixFormat = AV_PIX_FMT_YUV422P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ444P: + inputPixFormat = AV_PIX_FMT_YUV444P; + changeColorspaceDetails = true; + break; + case AV_PIX_FMT_YUVJ440P: + inputPixFormat = AV_PIX_FMT_YUV440P; + changeColorspaceDetails = true; + break; + default: + inputPixFormat = (AVPixelFormat)input_frame->format; + } + + convert_context = sws_getContext( + input_frame->width, + input_frame->height, + inputPixFormat, + 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(inputPixFormat), + camera_width, camera_height, + av_get_pix_fmt_name(imagePixFormat) + ); + if (changeColorspaceDetails) { + // change the range of input data by first reading the current color space and then setting it's range as yuvj. + int dummy[4]; + int srcRange, dstRange; + int brightness, contrast, saturation; + sws_getColorspaceDetails(convert_context, (int**)&dummy, &srcRange, (int**)&dummy, &dstRange, &brightness, &contrast, &saturation); + const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT); + srcRange = 1; // this marks that values are according to yuvj + sws_setColorspaceDetails(convert_context, coefs, srcRange, coefs, dstRange, + brightness, contrast, saturation); + } + + } + } + if (convert_context) { + if (!packet->image->Assign(packet->in_frame, convert_context, dest_frame)) { + delete packet->image; + packet->image = nullptr; + } + av_frame_unref(dest_frame); + } // 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(3, "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, "Timestamping"); + TimestampImage(packet->image, packet->timestamp); + } + + image_buffer[index]->Assign(*(packet->image)); + shared_timestamps[index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); + } // 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 = std::chrono::system_clock::to_time_t(packet->timestamp); + packetqueue.unlock(packet_lock); + return true; +} // end bool Monitor::Decode() + +void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { + if (!label_format[0]) return; // 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 = {}; + time_t ts_time_t = std::chrono::system_clock::to_time_t(ts_time); + strftime(label_time_text, sizeof(label_time_text), label_format.c_str(), localtime_r(&ts_time_t, &ts_tm)); + char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; - while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) { + + while (*s_ptr && ((unsigned int)(d_ptr - label_text) < (unsigned int) sizeof(label_text))) { if ( *s_ptr == config.timestamp_code_char[0] ) { 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' : @@ -2652,7 +2756,10 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con found_macro = true; break; case 'f' : - d_ptr += snprintf(d_ptr, sizeof(label_text)-(d_ptr-label_text), "%02ld", ts_time->tv_usec/10000); + typedef std::chrono::duration Centiseconds; + Centiseconds centi_sec = std::chrono::duration_cast( + ts_time.time_since_epoch() - std::chrono::duration_cast(ts_time.time_since_epoch())); + d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02lld", static_cast(centi_sec.count())); found_macro = true; break; } @@ -2669,151 +2776,149 @@ void Monitor::TimestampImage(Image *ts_image, const struct timeval *ts_time) con Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage -bool Monitor::closeEvent() { - if ( !event ) - return false; +void Monitor::closeEvent() { + if (!event) return; - delete event; + 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"); + } + Debug(1, "Starting thread to close event"); + close_event_thread = std::thread([](Event *e){ delete e; }, event); + Debug(1, "Nulling event"); event = nullptr; - if ( shared_data ) - video_store_data->recording = {}; - 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_ref.c_str(), config.record_diag_images_fifo); - delta_image.WriteJpeg(diag_path_delta.c_str(), config.record_diag_images_fifo); + if (config.record_diag_images) { + ref_image.WriteJpeg(diag_path_ref, config.record_diag_images_fifo); + delta_image.WriteJpeg(diag_path_delta, 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(kRGBBlack, 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; + Vector2 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 ) { - shared_data->alarm_x = alarm_centre.X(); - shared_data->alarm_y = alarm_centre.Y(); + 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, analysis_image_count); @@ -2823,23 +2928,23 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo // 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 ZM_HAS_V4L2 if ( camera->IsLocal() ) { 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 +#endif // ZM_HAS_V4L2 if ( camera->IsRemote() ) { RemoteCamera* cam = static_cast(camera.get()); sprintf( output+strlen(output), "Protocol : %s\n", cam->Protocol().c_str() ); @@ -2848,27 +2953,25 @@ bool Monitor::DumpSettings(char *output, bool verbose) { sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } else if ( camera->IsFile() ) { FileCamera* cam = static_cast(camera.get()); - sprintf( output+strlen(output), "Path : %s\n", cam->Path() ); + sprintf( output+strlen(output), "Path : %s\n", cam->Path().c_str() ); } -#if HAVE_LIBAVFORMAT else if ( camera->IsFfmpeg() ) { 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() ); -#if ZM_HAS_V4L + sprintf( output+strlen(output), "Width : %u\n", camera->Width() ); + sprintf( output+strlen(output), "Height : %u\n", camera->Height() ); +#if ZM_HAS_V4L2 if ( camera->IsLocal() ) { 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), "Label Coord : %d,%d\n", label_coord.X(), label_coord.Y() ); +#endif // ZM_HAS_V4L2 + 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 ); sprintf(output+strlen(output), "Warmup Count : %d\n", warmup_count ); @@ -2876,10 +2979,10 @@ bool Monitor::DumpSettings(char *output, bool verbose) { sprintf(output+strlen(output), "Post Event Count : %d\n", post_event_count ); sprintf(output+strlen(output), "Stream Replay Buffer : %d\n", stream_replay_buffer ); sprintf(output+strlen(output), "Alarm Frame Count : %d\n", alarm_frame_count ); - sprintf(output+strlen(output), "Section Length : %d\n", section_length); - sprintf(output+strlen(output), "Min Section Length : %d\n", min_section_length); - sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay?(double)DT_PREC_3/capture_delay:0.0); - sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay?(double)DT_PREC_3/alarm_capture_delay:0.0); + sprintf(output+strlen(output), "Section Length : %" PRIi64 "\n", static_cast(Seconds(section_length).count())); + sprintf(output+strlen(output), "Min Section Length : %" PRIi64 "\n", static_cast(Seconds(min_section_length).count())); + sprintf(output+strlen(output), "Maximum FPS : %.2f\n", capture_delay != Seconds(0) ? 1 / FPSeconds(capture_delay).count() : 0.0); + sprintf(output+strlen(output), "Alarm Maximum FPS : %.2f\n", alarm_capture_delay != Seconds(0) ? 1 / FPSeconds(alarm_capture_delay).count() : 0.0); sprintf(output+strlen(output), "Reference Blend %%ge : %d\n", ref_blend_perc); sprintf(output+strlen(output), "Alarm Reference Blend %%ge : %d\n", alarm_ref_blend_perc); sprintf(output+strlen(output), "Track Motion : %d\n", track_motion); @@ -2891,31 +2994,84 @@ 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 : %zu\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(); } +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 ) { - video_stream_id = camera->get_VideoStreamId(); + if (ret <= 0) return ret; - packetqueue.addStreamId(video_stream_id); + if ( -1 != camera->getVideoStreamId() ) { + video_stream_id = packetqueue.addStream(); + } - audio_stream_id = camera->get_AudioStreamId(); - if ( audio_stream_id >= 0 ) - packetqueue.addStreamId(audio_stream_id); + 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_buffer_count); + 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, + avcodec_get_name(videoStream->codecpar->codec_id) + ); + 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, + avcodec_get_name(audioStream->codecpar->codec_id) + ); + 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, "Restarting 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(2, "Failed to prime %d", ret); + 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() @@ -2923,13 +3079,32 @@ int Monitor::PrimeCapture() { int Monitor::PreCapture() const { return camera->PreCapture(); } 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, image_count, event->Id()); + Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id()); closeEvent(); } if (camera) camera->Close(); - packetqueue.clear(); return 1; } @@ -2939,63 +3114,64 @@ Monitor::Orientation Monitor::getOrientation() const { return orientation; } // So this should be done as the first task in the analysis thread startup. // This function is deprecated. void Monitor::get_ref_image() { - ZMPacket *snap = nullptr; + ZMLockedPacket *snap_lock = nullptr; if ( !analysis_it ) analysis_it = packetqueue.get_video_it(true); while ( ( - !( snap = packetqueue.get_packet(analysis_it)) + !( snap_lock = packetqueue.get_packet(analysis_it)) or - ( snap->packet.stream_index != video_stream_id ) + ( snap_lock->packet_->codec_type != AVMEDIA_TYPE_VIDEO ) or - ! snap->image + ! snap_lock->packet_->image ) and !zm_terminate) { - Debug(1, "Waiting for capture daemon lastwriteindex(%d) lastwritetime(%d)", - shared_data->last_write_index, shared_data->last_write_time); - if ( snap and ! snap->image ) { - snap->unlock(); + 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 ) + if (zm_terminate) return; + std::shared_ptr 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 ) { + 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"); } - snap->unlock(); -} + delete snap_lock; +} // get_ref_image std::vector Monitor::Groups() { // At the moment, only load groups once. - if ( !groups.size() ) { + 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); - MYSQL_RES *result = zmDbFetch(sql.c_str()); - if ( !result ) { + MYSQL_RES *result = zmDbFetch(sql); + if (!result) { Error("Can't load groups: %s", mysql_error(&dbconn)); return groups; } int n_groups = mysql_num_rows(result); Debug(1, "Got %d groups", n_groups); - while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { + groups.reserve(n_groups); + while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { groups.push_back(new Group(dbrow)); } - if ( mysql_errno(&dbconn) ) { + if (mysql_errno(&dbconn)) { Error("Can't fetch row: %s", mysql_error(&dbconn)); } mysql_free_result(result); @@ -3007,7 +3183,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 54facf26b..5b9a2ce46 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -22,11 +22,14 @@ #include "zm_define.h" #include "zm_camera.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_video.h" +#include "zm_utils.h" #include #include #include @@ -61,7 +64,7 @@ public: } Function; typedef enum { - LOCAL, + LOCAL=1, REMOTE, FILE, FFMPEG, @@ -81,7 +84,23 @@ public: } Orientation; typedef enum { - UNKNOWN=-1, + 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, IDLE, PREALARM, ALARM, @@ -102,7 +121,7 @@ protected: typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; - /* sizeof(SharedData) expected to be 344 bytes on 32bit and 64bit */ + /* sizeof(SharedData) expected to be 472 bytes on 32bit and 64bit */ typedef struct { uint32_t size; /* +0 */ int32_t last_write_index; /* +4 */ @@ -124,36 +143,35 @@ protected: uint8_t format; /* +55 */ uint32_t imagesize; /* +56 */ uint32_t last_frame_score; /* +60 */ - // uint32_t epadding1; /* +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; @@ -193,7 +211,7 @@ protected: #if ZM_MEM_MAPPED int map_fd; - char mem_file[PATH_MAX]; + std::string mem_file; #else // ZM_MEM_MAPPED int shm_id; #endif // ZM_MEM_MAPPED @@ -232,7 +250,7 @@ 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; @@ -248,8 +266,9 @@ protected: std::string user; std::string pass; std::string path; + std::string second_path; - char device[64]; + std::string device; int palette; int channel; int format; @@ -275,9 +294,7 @@ protected: int output_codec; std::string encoder; std::string output_container; - std::vector encoderparamsvec; _AVPIXELFORMAT imagePixFormat; - unsigned int subpixelorder; bool record_audio; // Whether to store the audio that we receive @@ -286,26 +303,25 @@ protected: 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 + 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 + Vector2 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, 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 + 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 + Seconds section_length; // How long events should last in continuous modes + Seconds 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 + Microseconds analysis_update_delay; // How long we wait before updating analysis parameters + Microseconds capture_delay; // How long we wait between capture frames + Microseconds 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 @@ -317,6 +333,8 @@ protected: 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. int capture_max_fps; @@ -336,17 +354,17 @@ protected: 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; + SystemTimePoint start_time; + SystemTimePoint last_fps_time; + SystemTimePoint last_analysis_fps_time; + SystemTimePoint auto_resume_time; unsigned int last_motion_score; EventCloseMode event_close_mode; #if ZM_MEM_MAPPED int map_fd; - char mem_file[PATH_MAX]; + std::string mem_file; #else // ZM_MEM_MAPPED int shm_id; #endif // ZM_MEM_MAPPED @@ -358,11 +376,12 @@ protected: struct timeval *shared_timestamps; unsigned char *shared_images; - ZMPacket *image_buffer; - ZMPacket next_buffer; /* Used by four field deinterlacing */ + std::vector image_buffer; 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; @@ -372,10 +391,14 @@ protected: 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; - - int n_zones; - Zone **zones; + std::vector zones; const unsigned char *privacy_bitmask; @@ -391,33 +414,52 @@ protected: 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); ~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 ServerId() { return server_id; } + inline const char *Name() const { return name.c_str(); } + inline unsigned int ServerId() const { return server_id; } inline Storage *getStorage() { if ( ! storage ) { storage = new Storage(storage_id); } return storage; } + inline CameraType GetType() const { return type; } inline Function GetFunction() const { return function; } inline PacketQueue * GetPacketQueue() { return &packetqueue; } inline bool Enabled() const { @@ -428,12 +470,8 @@ public: inline bool DecodingEnabled() const { return decoding_enabled; } - inline const char *EventPrefix() const { return event_prefix; } + inline const char *EventPrefix() const { return event_prefix.c_str(); } inline bool Ready() const { - if ( function <= MONITOR ) { - Error("Should not be calling Ready if the function doesn't include motion detection"); - return false; - } if ( image_count >= ready_count ) { return true; } @@ -447,7 +485,7 @@ public: } inline bool Exif() const { return embed_exif; } inline bool RTSPServer() const { return rtsp_server; } - inline bool RecordAudio() { return record_audio; } + inline bool RecordAudio() const { return record_audio; } /* inline Purpose Purpose() { return purpose }; @@ -461,9 +499,11 @@ public: 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* GetEncoderParams() const { return &encoderparamsvec; } const std::string &GetEncoderOptions() const { return encoderparams; } int OutputCodec() const { return output_codec; } const std::string &Encoder() const { return encoder; } @@ -472,39 +512,50 @@ public: uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } 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(const struct timeval &t) { video_store_data->recording = t; } + SystemTimePoint GetVideoWriterStartTime() const { + return SystemTimePoint(zm::chrono::duration_cast(video_store_data->recording)); + } + void SetVideoWriterStartTime(SystemTimePoint t) { + video_store_data->recording = zm::chrono::duration_cast(t.time_since_epoch()); + } unsigned int GetPreEventCount() const { return pre_event_count; }; int32_t GetImageBufferCount() const { return image_buffer_count; }; State GetState() const { return (State)shared_data->state; } - AVStream *GetAudioStream() const { return camera ? camera->get_AudioStream() : nullptr; }; - AVCodecContext *GetAudioCodecContext() const { return camera ? camera->get_AudioCodecContext() : nullptr; }; - AVStream *GetVideoStream() const { return camera ? camera->get_VideoStream() : nullptr; }; - AVCodecContext *GetVideoCodecContext() const { return camera ? camera->get_VideoCodecContext() : nullptr; }; + 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; }; + + std::string GetSecondPath() const { return second_path; }; + std::string GetVideoFifoPath() const { return shared_data ? shared_data->video_fifo_path : ""; }; + std::string GetAudioFifoPath() const { return shared_data ? shared_data->audio_fifo_path : ""; }; + 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; + SystemTimePoint GetTimestamp(int index = -1) const; void UpdateAdaptiveSkip(); useconds_t GetAnalysisRate(); - unsigned int GetAnalysisUpdateDelay() const { return analysis_update_delay; } + Microseconds GetAnalysisUpdateDelay() const { return analysis_update_delay; } unsigned int GetCaptureMaxFPS() const { return capture_max_fps; } - int GetCaptureDelay() const { return capture_delay; } - int GetAlarmCaptureDelay() const { return alarm_capture_delay; } + Microseconds GetCaptureDelay() const { return capture_delay; } + Microseconds GetAlarmCaptureDelay() const { return alarm_capture_delay; } unsigned int GetLastReadIndex() const; unsigned int GetLastWriteIndex() const; uint64_t GetLastEventId() const; double GetFPS() const; - void UpdateAnalysisFPS(); - void UpdateCaptureFPS(); + void UpdateFPS(); void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" ); void ForceAlarmOff(); void CancelForced(); 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; } + SystemTimePoint GetStartupTime() const { return std::chrono::system_clock::from_time_t(shared_data->startup_time); } + void SetStartupTime(SystemTimePoint time) { shared_data->startup_time = std::chrono::system_clock::to_time_t(time); } + void SetHeartbeatTime(SystemTimePoint time) { + shared_data->zmc_heartbeat_time = std::chrono::system_clock::to_time_t(time); + } void get_ref_image(); int LabelSize() const { return label_size; } @@ -515,10 +566,14 @@ public: void actionSuspend(); void actionResume(); - int actionBrightness( int p_brightness=-1 ); - int actionHue( int p_hue=-1 ); - int actionColour( int p_colour=-1 ); - int actionContrast( int p_contrast=-1 ); + int actionBrightness(int p_brightness); + int actionBrightness(); + int actionHue(int p_hue); + int actionHue(); + int actionColour(int p_colour); + int actionColour(); + int actionContrast(int p_contrast); + int actionContrast(); int PrimeCapture(); int PreCapture() const; @@ -533,9 +588,10 @@ public: //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 TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; + void closeEvent(); void Reload(); void ReloadZones(); @@ -546,30 +602,27 @@ public: std::vector Groups(); StringVector GroupNames(); - static std::vector> LoadMonitors(std::string sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. -#if ZM_HAS_V4L + static std::vector> LoadMonitors(const std::string &sql, Purpose purpose); // Returns # of Monitors loaded, 0 on failure. +#if ZM_HAS_V4L2 static std::vector> LoadLocalMonitors(const char *device, Purpose purpose); -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 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 std::vector> LoadFfmpegMonitors(const char *file, Purpose purpose); -#endif // HAVE_LIBAVFORMAT 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 ); //void StreamImagesZip( int scale=100, int maxfps=10, time_t ttl=0 ); -#if HAVE_LIBAVCODEC //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 ); -#endif // HAVE_LIBAVCODEC 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() const { 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 83524b2ff..9bc04a311 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -26,12 +26,12 @@ #include #include #include +#include + #ifdef __FreeBSD__ #include #endif -const int MAX_SLEEP_USEC = 1000000; // 1 sec - bool MonitorStream::checkSwapPath(const char *path, bool create_path) { struct stat stat_buf; if ( stat(path, &stat_buf) < 0 ) { @@ -85,11 +85,11 @@ void MonitorStream::processCommand(const CmdMsg *msg) { Debug(1, "Got PAUSE command"); paused = true; delayed = true; - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; break; case CMD_PLAY : Debug(1, "Got PLAY command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -97,7 +97,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_VARPLAY : Debug(1, "Got VARPLAY command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -110,7 +110,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_FASTFWD : Debug(1, "Got FAST FWD command"); - if ( paused ) { + if (paused) { paused = false; delayed = true; } @@ -135,27 +135,27 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } break; case CMD_SLOWFWD : - Debug( 1, "Got SLOW FWD command" ); + Debug(1, "Got SLOW FWD command"); paused = true; delayed = true; replay_rate = ZM_RATE_BASE; step = 1; break; case CMD_SLOWREV : - Debug( 1, "Got SLOW REV command" ); + Debug(1, "Got SLOW REV command"); paused = true; delayed = true; replay_rate = ZM_RATE_BASE; step = -1; break; case CMD_FASTREV : - Debug( 1, "Got FAST REV command" ); - if ( paused ) { + Debug(1, "Got FAST REV command"); + if (paused) { paused = false; delayed = true; } // Set play rate - switch ( replay_rate ) { + switch (replay_rate) { case -2 * ZM_RATE_BASE : replay_rate = -5 * ZM_RATE_BASE; break; @@ -229,6 +229,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { break; case CMD_QUIT : Info("User initiated exit - CMD_QUIT"); + zm_terminate = true; break; case CMD_QUERY : Debug(1, "Got QUERY command, sending STATUS"); @@ -255,7 +256,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } status_data; status_data.id = monitor->Id(); - if ( ! monitor->ShmValid() ) { + if (!monitor->ShmValid()) { status_data.fps = 0.0; status_data.capture_fps = 0.0; status_data.analysis_fps = 0.0; @@ -265,7 +266,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.forced = false; status_data.buffer_level = 0; } else { - status_data.fps = monitor->GetFPS(); + FPSeconds elapsed = now - last_fps_update; + if (elapsed.count()) { + actual_fps = (frame_count - last_frame_count) / elapsed.count(); + last_frame_count = frame_count; + last_fps_update = now; + } + + status_data.fps = actual_fps; status_data.capture_fps = monitor->get_capture_fps(); status_data.analysis_fps = monitor->get_analysis_fps(); status_data.state = monitor->shared_data->state; @@ -280,7 +288,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { status_data.delayed = delayed; status_data.paused = paused; status_data.rate = replay_rate; - status_data.delay = TV_2_FLOAT(now) - TV_2_FLOAT(last_frame_timestamp); + status_data.delay = FPSeconds(now - last_frame_timestamp).count(); status_data.zoom = zoom; 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, @@ -308,51 +316,39 @@ void MonitorStream::processCommand(const CmdMsg *msg) { } } 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 ) { - zm_terminate = true; - Debug(2, "Quitting"); - return; - } - - //Debug(2,"Updating framerate"); - //updateFrameRate(monitor->GetFPS()); } // end void MonitorStream::processCommand(const CmdMsg *msg) -bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { +bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); if ( ( type != STREAM_JPEG ) || - ( (!config.timestamp_on_capture) && timestamp ) + (!config.timestamp_on_capture) ) send_raw = false; - if ( !send_raw ) { - Image temp_image(filepath); + if (!send_raw) { + Image temp_image(filepath.c_str()); return sendFrame(&temp_image, timestamp); } else { int img_buffer_size = 0; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; - FILE *fdj = nullptr; - if ( (fdj = fopen(filepath, "r")) ) { + if (FILE *fdj = fopen(filepath.c_str(), "r")) { img_buffer_size = fread(img_buffer, 1, sizeof(img_buffer), fdj); fclose(fdj); } else { - Error("Can't open %s: %s", filepath, strerror(errno)); + Error("Can't open %s: %s", filepath.c_str(), strerror(errno)); return false; } // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday(&frameStartTime, nullptr); + TimePoint send_start_time = std::chrono::steady_clock::now(); if ( - (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %d.%06d\r\n\r\n", - img_buffer_size, (int)timestamp->tv_sec, (int)timestamp->tv_usec)) + (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.6f\r\n\r\n", + img_buffer_size, std::chrono::duration_cast(timestamp.time_since_epoch()).count())) || (fwrite(img_buffer, img_buffer_size, 1, stdout) != 1) ) { @@ -363,52 +359,52 @@ bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, nullptr); + TimePoint send_end_time = std::chrono::steady_clock::now(); + TimePoint::duration frame_send_time = send_end_time - send_start_time; - int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime); - if ( frameSendTime > 1000/maxfps ) { + if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) { maxfps /= 2; - Info("Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps); + Info("Frame send time %" PRIi64 " ms too slow, throttling maxfps to %.2f", + static_cast(std::chrono::duration_cast(frame_send_time).count()), + maxfps); } - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } return false; -} // end bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) +} -bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { +bool MonitorStream::sendFrame(Image *image, SystemTimePoint timestamp) { Image *send_image = prepareImage(image); - if ( !config.timestamp_on_capture && timestamp ) + if (!config.timestamp_on_capture) { monitor->TimestampImage(send_image, timestamp); + } fputs("--" BOUNDARY "\r\n", stdout); -#if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) { if ( !vid_stream ) { vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height()); fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } - static struct timeval base_time; - struct DeltaTimeval delta_time; - if ( !frame_count ) - base_time = *timestamp; - DELTA_TIMEVAL(delta_time, *timestamp, base_time, DT_PREC_3); - /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta); - } else -#endif // HAVE_LIBAVCODEC - { + + static SystemTimePoint base_time; + if (!frame_count) { + base_time = timestamp; + } + SystemTimePoint::duration delta_time = timestamp - base_time; + + /* double pts = */ vid_stream->EncodeFrame(send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.count()); + } else { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; int img_buffer_size = 0; unsigned char *img_buffer = temp_img_buffer; // Calculate how long it takes to actually send the frame - struct timeval frameStartTime; - gettimeofday(&frameStartTime, nullptr); + TimePoint send_start_time = std::chrono::steady_clock::now(); switch ( type ) { case STREAM_JPEG : @@ -417,7 +413,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { break; case STREAM_RAW : fputs("Content-Type: image/x-rgb\r\n", stdout); - img_buffer = (uint8_t*)send_image->Buffer(); + img_buffer = send_image->Buffer(); img_buffer_size = send_image->Size(); break; case STREAM_ZIP : @@ -436,8 +432,8 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { return false; } if ( - ( 0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %d.%06d\r\n\r\n", - img_buffer_size, (int)timestamp->tv_sec, (int)timestamp->tv_usec) ) + (0 > fprintf(stdout, "Content-Length: %d\r\nX-Timestamp: %.6f\r\n\r\n", + img_buffer_size, std::chrono::duration_cast(timestamp.time_since_epoch()).count())) || (fwrite(img_buffer, img_buffer_size, 1, stdout) != 1) ) { @@ -450,51 +446,62 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { fputs("\r\n", stdout); fflush(stdout); - struct timeval frameEndTime; - gettimeofday(&frameEndTime, nullptr); + TimePoint send_end_time = std::chrono::steady_clock::now(); + TimePoint::duration frame_send_time = send_end_time - send_start_time; - int frameSendTime = tvDiffMsec(frameStartTime, frameEndTime); - if ( frameSendTime > 1000/maxfps ) { + if (frame_send_time > Milliseconds(lround(Milliseconds::period::den / maxfps))) { maxfps /= 1.5; - Warning("Frame send time %d msec too slow, throttling maxfps to %.2f", - frameSendTime, maxfps); + Warning("Frame send time %" PRIi64 " msec too slow, throttling maxfps to %.2f", + static_cast(std::chrono::duration_cast(frame_send_time).count()), + maxfps); } } // Not mpeg - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; -} // 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 { + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); + 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 int32_t last_read_index = monitor->image_buffer_count; - time_t stream_start_time; - time(&stream_start_time); + SystemTimePoint stream_start_time = std::chrono::system_clock::now(); frame_count = 0; @@ -508,9 +515,9 @@ void MonitorStream::runStream() { // Last image and timestamp when paused, will be resent occasionally to prevent timeout Image *paused_image = nullptr; - struct timeval paused_timestamp; + SystemTimePoint paused_timestamp; - if ( connkey && ( playback_buffer > 0 ) ) { + if (connkey && (playback_buffer > 0)) { // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id const int max_swap_len_suffix = 15; @@ -519,166 +526,150 @@ void MonitorStream::runStream() { int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; - if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { + if (total_swap_path_length + max_swap_len_suffix > PATH_MAX) { Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); } else { swap_path = staticConfig.PATH_SWAP; Debug(3, "Checking swap path folder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { swap_path += stringtf("/zmswap-m%d", monitor->Id()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { swap_path += stringtf("/zmswap-q%06d", connkey); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); - if ( checkSwapPath(swap_path.c_str(), true) ) { + if (checkSwapPath(swap_path.c_str(), true)) { buffered_playback = true; } } } - if ( !buffered_playback ) { + if (!buffered_playback) { Error("Unable to validate swap image path, disabling buffered playback"); } else { Debug(2, "Assigning temporary buffer"); temp_image_buffer = new SwapImage[temp_image_buffer_count]; - memset(temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count); Debug(2, "Assigned temporary buffer"); } } } else { Debug(2, "Not using playback_buffer"); - } // end if connkey & 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 ) { - bool got_command = false; - if ( feof(stdout) ) { + while (!zm_terminate) { + if (feof(stdout)) { Debug(2, "feof stdout"); break; - } else if ( ferror(stdout) ) { + } 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; } - gettimeofday(&now, nullptr); + now = std::chrono::system_clock::now(); bool was_paused = paused; - if ( connkey ) { - while ( checkCommandQueue() && !zm_terminate ) { + bool got_command = false; // commands like zoom should output a frame even if paused + if (connkey) { + while (checkCommandQueue() && !zm_terminate) { // Loop in here until all commands are processed. Debug(2, "Have checking command Queue for connkey: %d", connkey); got_command = true; } // Update modified time of the socket .lock file so that we can tell which ones are stale. - if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { + if (now - last_comm_update > Hours(1)) { touch(sock_path_lock); last_comm_update = now; } } // end if connkey - if ( paused ) { - if ( !was_paused ) { + if (paused) { + if (!was_paused) { int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; Debug(1, "Saving paused image from index %d",index); - paused_image = new Image(*monitor->image_buffer[index].image); - paused_timestamp = *(monitor->image_buffer[index].timestamp); + paused_image = new Image(*monitor->image_buffer[index]); + paused_timestamp = SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); } - } else if ( paused_image ) { - Debug(1, "Clearing paused_image"); + } else if (paused_image) { delete paused_image; paused_image = nullptr; } - if ( buffered_playback && delayed ) { - if ( temp_read_index == temp_write_index ) { + if (buffered_playback && delayed) { + if (temp_read_index == temp_write_index) { // Go back to live viewing Debug(1, "Exceeded temporary streaming buffer"); - // Clear paused flag paused = false; - // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; } else { - if ( !paused ) { + if (!paused) { int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - // Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; - if ( !swap_image->valid ) { + if (!swap_image->valid) { paused = true; delayed = true; temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); } else { - // Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); - double expected_delta_time = ((TV_2_FLOAT(swap_image->timestamp) - TV_2_FLOAT(last_frame_timestamp)) * ZM_RATE_BASE)/replay_rate; - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; + FPSeconds expected_delta_time = ((FPSeconds(swap_image->timestamp - last_frame_timestamp)) * ZM_RATE_BASE) / replay_rate; + SystemTimePoint::duration actual_delta_time = now - last_frame_sent; - // Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due - if ( actual_delta_time > expected_delta_time ) { + if (actual_delta_time > expected_delta_time) { // Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); - if ( temp_index%frame_mod == 0 ) { + if ((temp_index % frame_mod) == 0) { Debug(2, "Sending delayed frame %d", temp_index); // Send the next frame - if ( ! sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { + if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { zm_terminate = true; } - memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); + frame_count++; + last_frame_timestamp = swap_image->timestamp; // frame_sent = true; } - temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count); + temp_read_index = MOD_ADD(temp_read_index, (replay_rate > 0 ? 1 : -1), temp_image_buffer_count); } } - } else if ( step != 0 ) { + } else if (step != 0) { temp_read_index = MOD_ADD(temp_read_index, (step>0?1:-1), temp_image_buffer_count); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; // Send the next frame - if ( !sendFrame( + if (!sendFrame( temp_image_buffer[temp_read_index].file_name, - &temp_image_buffer[temp_read_index].timestamp - ) ) { + temp_image_buffer[temp_read_index].timestamp) + ) { zm_terminate = true; } - memcpy( - &last_frame_timestamp, - &(swap_image->timestamp), - sizeof(last_frame_timestamp) - ); + frame_count++; + + last_frame_timestamp = swap_image->timestamp; // frame_sent = true; step = 0; } else { //paused? int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( got_command || (actual_delta_time > 5) ) { + if (got_command || (now - last_frame_sent > Seconds(5))) { // Send keepalive Debug(2, "Sending keepalive frame %d", temp_index); // Send the next frame - if ( !sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { + if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { zm_terminate = true; } + frame_count++; // frame_sent = true; } } // end if (!paused) or step or paused } // end if have exceeded buffer or not - if ( temp_read_index == temp_write_index ) { + if (temp_read_index == temp_write_index) { // Go back to live viewing Warning("Rewound over write index, resuming live play"); // Clear paused flag @@ -687,33 +678,33 @@ void MonitorStream::runStream() { delayed = false; replay_rate = ZM_RATE_BASE; } - } // end if ( buffered_playback && delayed ) + } // end if (buffered_playback && delayed) - if ( last_read_index != monitor->shared_data->last_write_index ) { + 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 - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { - if ( !paused && !delayed ) { + int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; + 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 // - ZMPacket *snap = &monitor->image_buffer[index]; + // Perhaps we should use NOW instead. + last_frame_timestamp = + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); + Image *image = monitor->image_buffer[index]; - if ( !sendFrame(snap->image, snap->timestamp) ) { + if (!sendFrame(image, last_frame_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 ) { + frame_count++; + 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) ) { + if (!sendFrame(image, last_frame_timestamp)) { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; break; @@ -722,55 +713,54 @@ void MonitorStream::runStream() { temp_read_index = temp_write_index; } else { - if ( delayed && !buffered_playback ) { + if (delayed && !buffered_playback) { Debug(2, "Can't delay when not buffering."); delayed = false; } - if ( last_zoom != zoom ) { + if (last_zoom != zoom) { Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom); - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; + frame_count++; + frame_count++; } else { - double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - if ( actual_delta_time > 5 ) { - if ( paused_image ) { + SystemTimePoint::duration actual_delta_time = now - last_frame_sent; + if (actual_delta_time > Seconds(5)) { + if (paused_image) { // Send keepalive - Debug(2, "Sending keepalive frame because delta time %.2f > 5", - actual_delta_time); + Debug(2, "Sending keepalive frame because delta time %.2f s > 5 s", + FPSeconds(actual_delta_time).count()); // Send the next frame - if ( !sendFrame(paused_image, &paused_timestamp) ) + if (!sendFrame(paused_image, paused_timestamp)) zm_terminate = true; + frame_count++; } 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 + } else { + frame_count++; } // end if should send frame - if ( buffered_playback && !paused ) { - if ( monitor->shared_data->valid ) { - if ( monitor->shared_timestamps[index].tv_sec ) { + if (buffered_playback && !paused) { + if (monitor->shared_data->valid) { + 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 ) { - snprintf( - temp_image_buffer[temp_index].file_name, - sizeof(temp_image_buffer[0].file_name), - "%s/zmswap-i%05d.jpg", - swap_path.c_str(), - temp_index); + temp_image_buffer[temp_index].file_name = stringtf("%s/zmswap-i%05d.jpg", swap_path.c_str(), temp_index); temp_image_buffer[temp_index].valid = true; } - 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_image_buffer[temp_index].timestamp = + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index])); + monitor->image_buffer[index]->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 ) { + if (temp_write_index == temp_read_index) { // Go back to live viewing Warning("Exceeded temporary buffer, resuming live play"); paused = false; @@ -784,64 +774,67 @@ void MonitorStream::runStream() { Warning("Unable to store frame as shared memory invalid"); } } // end if buffered playback - frame_count++; } else { Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); } // 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 ) { + FPSeconds sleep_time = + FPSeconds(ZM_RATE_BASE / ((base_fps ? base_fps : 1) * (replay_rate ? abs(replay_rate * 2) : 2))); + + if (sleep_time > MonitorStream::MAX_SLEEP) { // 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; + Debug(3, "Sleeping for MAX_SLEEP_USEC %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); } else { - Debug(3, "Sleeping for %dus", sleep_time); + Debug(3, "Sleeping for %" PRIi64 " us", + static_cast(std::chrono::duration_cast(sleep_time).count())); } - 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); - break; - } + std::this_thread::sleep_for(sleep_time); + + if (ttl > Seconds(0) && (now - stream_start_time) > ttl) { + Debug(2, "now - start > ttl (%" PRIi64 " us). break", + static_cast(std::chrono::duration_cast(ttl).count())); + break; } - if ( !last_frame_sent ) { + + if (last_frame_sent.time_since_epoch() == Seconds(0)) { // If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value. - last_frame_sent = now.tv_sec; + last_frame_sent = now; Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)", frame_mod, frame_count); } - } // end while + } // end while ! zm_terminate - if ( buffered_playback ) { + if (buffered_playback) { Debug(1, "Cleaning swap files from %s", swap_path.c_str()); - struct stat stat_buf; - if ( stat(swap_path.c_str(), &stat_buf) < 0 ) { - if ( errno != ENOENT ) { + struct stat stat_buf = {}; + if (stat(swap_path.c_str(), &stat_buf) < 0) { + if (errno != ENOENT) { Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno)); } - } else if ( !S_ISDIR(stat_buf.st_mode) ) { + } else if (!S_ISDIR(stat_buf.st_mode)) { Error("Swap image path '%s' is not a directory", swap_path.c_str()); } else { - char glob_pattern[PATH_MAX] = ""; - - snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str()); + std::string glob_pattern = stringtf("%s/*.*", swap_path.c_str()); glob_t pglob; - int glob_status = glob(glob_pattern, 0, 0, &pglob); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { - Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); + + int glob_status = glob(glob_pattern.c_str(), 0, 0, &pglob); + if (glob_status != 0) { + if (glob_status < 0) { + Error("Can't glob '%s': %s", glob_pattern.c_str(), strerror(errno)); } else { - Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); + Debug(1, "Can't glob '%s': %d", glob_pattern.c_str(), glob_status); } } else { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { - if ( unlink(pglob.gl_pathv[i]) < 0 ) { + for (unsigned int i = 0; i < pglob.gl_pathc; i++) { + if (unlink(pglob.gl_pathv[i]) < 0) { Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno)); } } } globfree(&pglob); - if ( rmdir(swap_path.c_str()) < 0 ) { + if (rmdir(swap_path.c_str()) < 0) { Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno)); } } // end if checking for swap_path @@ -854,22 +847,22 @@ void MonitorStream::SingleImage(int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; - while ( monitor->shared_data->last_write_index >= monitor->image_buffer_count ) { + while ((monitor->shared_data->last_write_index >= monitor->image_buffer_count) and !zm_terminate) { Debug(1, "Waiting for capture to begin"); - usleep(100000); + std::this_thread::sleep_for(Milliseconds(100)); } 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; + Image *snap_image = monitor->image_buffer[index]; if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign(*snap_image); scaled_image.Scale(scale); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) { - monitor->TimestampImage(snap_image, snap->timestamp); + if (!config.timestamp_on_capture) { + monitor->TimestampImage(snap_image, + SystemTimePoint(zm::chrono::duration_cast(monitor->shared_timestamps[index]))); } snap_image->EncodeJpeg(img_buffer, &img_buffer_size); @@ -921,7 +914,7 @@ 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); diff --git a/src/zm_monitorstream.h b/src/zm_monitorstream.h index 0c1f92777..04d2be25c 100644 --- a/src/zm_monitorstream.h +++ b/src/zm_monitorstream.h @@ -21,15 +21,14 @@ #define ZM_MONITORSTREAM_H #include "zm_stream.h" -#include class MonitorStream : public StreamBase { protected: - typedef struct SwapImage { - bool valid; - struct timeval timestamp; - char file_name[PATH_MAX]; - } SwapImage; + struct SwapImage { + bool valid = false; + SystemTimePoint timestamp; + std::string file_name; + }; private: SwapImage *temp_image_buffer; @@ -38,15 +37,14 @@ class MonitorStream : public StreamBase { int temp_write_index; protected: - time_t ttl; + Microseconds ttl; int playback_buffer; bool delayed; - int frame_count; protected: bool checkSwapPath(const char *path, bool create_path); - bool sendFrame(const char *filepath, struct timeval *timestamp); - bool sendFrame(Image *image, struct timeval *timestamp); + bool sendFrame(const std::string &filepath, SystemTimePoint timestamp); + bool sendFrame(Image *image, SystemTimePoint timestamp); void processCommand(const CmdMsg *msg) override; void SingleImage(int scale=100); void SingleImageRaw(int scale=100); @@ -55,21 +53,21 @@ class MonitorStream : public StreamBase { #endif public: - MonitorStream() : + 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) { - } + delayed(false) + {} + void setStreamBuffer(int p_playback_buffer) { playback_buffer = p_playback_buffer; } void setStreamTTL(time_t p_ttl) { - ttl = p_ttl; + ttl = Seconds(p_ttl); } bool setStreamStart(int monitor_id) { return loadMonitor(monitor_id); diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 3b298705b..b8f7eeb66 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -21,13 +21,7 @@ #include "zm_logger.h" #include "zm_rgb.h" -#include - -#if HAVE_LIBAVCODEC -extern "C" { -#include -#include -} +#include "zm_time.h" bool VideoStream::initialised = false; @@ -46,61 +40,10 @@ void VideoStream::Initialise( ) { void VideoStream::SetupFormat( ) { /* allocate the output media context */ ofc = nullptr; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) avformat_alloc_output_context2(&ofc, nullptr, format, filename); -#else - AVFormatContext *s = avformat_alloc_context(); - if ( !s ) { - Fatal("avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc)); - return; - } - AVOutputFormat *oformat; - if ( format ) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, nullptr, nullptr); -#else - oformat = guess_format(format, nullptr, nullptr); -#endif - if ( !oformat ) { - Fatal("Requested output format '%s' is not a suitable output format", format); - } - } else { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(nullptr, filename, nullptr); -#else - oformat = guess_format(nullptr, filename, nullptr); -#endif - if ( !oformat ) { - Fatal("Unable to find a suitable output format for '%s'", format); - } - } - s->oformat = oformat; - - if ( s->oformat->priv_data_size > 0 ) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if ( !(s->priv_data) ) { - Fatal("Could not allocate private data for output format."); - } -#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) - if ( s->oformat->priv_class ) { - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } -#endif - } else { - Debug(1, "No allocating priv_data"); - s->priv_data = nullptr; - } - - if ( filename ) { - snprintf(s->filename, sizeof(s->filename), "%s", filename); - } - - ofc = s; -#endif if ( !ofc ) { - Fatal("avformat_alloc_..._context failed: %d", ofc); + Fatal("avformat_alloc_..._context failed"); } of = ofc->oformat; @@ -161,11 +104,7 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei codec_id = a->id; Debug(1, "Using codec \"%s\"", codec_name); } else { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Debug(1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name(codec_id)); -#else - Debug(1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id); -#endif } } @@ -175,24 +114,12 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei if ( codec_id != AV_CODEC_ID_NONE ) { codec = avcodec_find_encoder(codec_id); if ( !codec ) { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Fatal("Could not find encoder for '%s'", avcodec_get_name(codec_id)); -#else - Fatal("Could not find encoder for '%d'", codec_id); -#endif } -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Debug(1, "Found encoder for '%s'", avcodec_get_name(codec_id)); -#else - Debug(1, "Found encoder for '%d'", codec_id); -#endif -#if LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) ost = avformat_new_stream( ofc, codec ); -#else - ost = av_new_stream( ofc, 0 ); -#endif if ( !ost ) { Fatal( "Could not alloc stream" ); @@ -201,13 +128,8 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei Debug( 1, "Allocated stream (%d) !=? (%d)", ost->id , ofc->nb_streams - 1 ); ost->id = ofc->nb_streams - 1; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - codec_context = avcodec_alloc_context3(nullptr); //avcodec_parameters_to_context(codec_context, ost->codecpar); -#else - codec_context = ost->codec; -#endif codec_context->codec_id = codec->id; codec_context->codec_type = codec->type; @@ -216,11 +138,7 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei if ( bitrate <= 100 ) { // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. // This gets rid of artifacts in the beginning of the movie; and well, even quality. -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) codec_context->flags |= AV_CODEC_FLAG_QSCALE; -#else - codec_context->flags |= CODEC_FLAG_QSCALE; -#endif codec_context->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); } else { codec_context->bit_rate = bitrate; @@ -246,15 +164,10 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei // some formats want stream headers to be separate if ( of->flags & AVFMT_GLOBALHEADER ) -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_parameters_from_context(ost->codecpar, codec_context); zm_dump_codecpar(ost->codecpar); -#endif } else { Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); } @@ -291,12 +204,8 @@ bool VideoStream::OpenStream( ) { Debug(1,"Opening codec"); /* open the codec */ -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (ret = avcodec_open(codec_context, codec)) < 0 ) -#else - if ( (ret = avcodec_open2(codec_context, codec, nullptr)) < 0 ) -#endif - { + + if ((ret = avcodec_open2(codec_context, codec, nullptr)) < 0) { Error("Could not open codec. Error code %d \"%s\"", ret, av_err2str(ret)); return false; } @@ -313,11 +222,7 @@ bool VideoStream::OpenStream( ) { opicture->height = codec_context->height; opicture->format = codec_context->pix_fmt; -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int size = av_image_get_buffer_size(codec_context->pix_fmt, codec_context->width, codec_context->height, 1); -#else - int size = avpicture_get_size(codec_context->pix_fmt, codec_context->width, codec_context->height); -#endif uint8_t *opicture_buf = (uint8_t *)av_malloc(size); if ( !opicture_buf ) { @@ -325,59 +230,39 @@ bool VideoStream::OpenStream( ) { Error( "Could not allocate opicture_buf" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(opicture->data, opicture->linesize, opicture_buf, codec_context->pix_fmt, codec_context->width, codec_context->height, 1); -#else - avpicture_fill( (AVPicture *)opicture, opicture_buf, codec_context->pix_fmt, - codec_context->width, codec_context->height ); -#endif /* if the output format is not identical to the input format, then a temporary picture is needed too. It is then converted to the required output format */ tmp_opicture = nullptr; if ( codec_context->pix_fmt != pf ) { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - tmp_opicture = av_frame_alloc( ); -#else - tmp_opicture = avcodec_alloc_frame( ); -#endif + tmp_opicture = av_frame_alloc(); + if ( !tmp_opicture ) { Error( "Could not allocate tmp_opicture" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int size = av_image_get_buffer_size( pf, codec_context->width, codec_context->height,1 ); -#else - int size = avpicture_get_size( pf, codec_context->width, codec_context->height ); -#endif + uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); if ( !tmp_opicture_buf ) { av_frame_free( &tmp_opicture ); Error( "Could not allocate tmp_opicture_buf" ); return false; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(tmp_opicture->data, tmp_opicture->linesize, tmp_opicture_buf, pf, codec_context->width, codec_context->height, 1); -#else - avpicture_fill( (AVPicture *)tmp_opicture, - tmp_opicture_buf, pf, codec_context->width, codec_context->height ); -#endif } } // end if ost /* open the output file, if needed */ if ( !(of->flags & AVFMT_NOFILE) ) { -#if LIBAVFORMAT_VERSION_CHECK(53, 15, 0, 21, 0) ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr ); -#elif LIBAVFORMAT_VERSION_CHECK(52, 102, 0, 102, 0) - ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); -#else - ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); -#endif + if ( ret < 0 ) { Error("Could not open '%s'", filename); return false; @@ -390,12 +275,8 @@ bool VideoStream::OpenStream( ) { } video_outbuf = nullptr; -#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { -#else - if ( !(of->flags & AVFMT_RAWPICTURE) ) { -#endif /* allocate output buffer */ /* XXX: API change will be done */ // TODO: Make buffer dynamic. @@ -406,17 +287,9 @@ bool VideoStream::OpenStream( ) { } } -#if LIBAVFORMAT_VERSION_CHECK(52, 101, 0, 101, 0) av_dump_format(ofc, 0, filename, 1); -#else - dump_format(ofc, 0, filename, 1); -#endif -#if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - ret = av_write_header(ofc); -#else ret = avformat_write_header(ofc, nullptr); -#endif if ( ret < 0 ) { Error("?_write_header failed with error %d \"%s\"", ret, av_err2str(ret)); @@ -531,11 +404,7 @@ VideoStream::~VideoStream( ) { if ( !(of->flags & AVFMT_NOFILE) ) { /* close the output file */ -#if LIBAVFORMAT_VERSION_CHECK(52, 105, 0, 105, 0) avio_close( ofc->pb ); -#else - url_fclose( ofc->pb ); -#endif } /* free the stream */ @@ -594,20 +463,14 @@ double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _a double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { if ( codec_context->pix_fmt != pf ) { -#ifdef HAVE_LIBSWSCALE static struct SwsContext *img_convert_ctx = nullptr; -#endif // HAVE_LIBSWSCALE memcpy( tmp_opicture->data[0], buffer, buffer_size ); -#ifdef HAVE_LIBSWSCALE if ( !img_convert_ctx ) { img_convert_ctx = sws_getCachedContext( nullptr, codec_context->width, codec_context->height, pf, codec_context->width, codec_context->height, codec_context->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr ); if ( !img_convert_ctx ) Panic( "Unable to initialise image scaling context" ); } sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, codec_context->height, opicture->data, opicture->linesize ); -#else // HAVE_LIBSWSCALE - Fatal( "swscale is required for MPEG mode" ); -#endif // HAVE_LIBSWSCALE } else { memcpy( opicture->data[0], buffer, buffer_size ); } @@ -615,28 +478,18 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, AVPacket *pkt = packet_buffers[packet_index]; av_init_packet( pkt ); - int got_packet = 0; -#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) + int got_packet = 0; if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { -#else - if ( of->flags & AVFMT_RAWPICTURE ) { -#endif - -#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) pkt->flags |= AV_PKT_FLAG_KEY; -#else - pkt->flags |= PKT_FLAG_KEY; -#endif pkt->stream_index = ost->index; pkt->data = (uint8_t *)opicture_ptr; pkt->size = sizeof (AVPicture); - got_packet = 1; + got_packet = 1; } else { opicture_ptr->pts = codec_context->frame_number; opicture_ptr->quality = codec_context->global_quality; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_send_frame(codec_context, opicture_ptr); int ret = avcodec_receive_packet(codec_context, pkt); if ( ret < 0 ) { @@ -647,28 +500,11 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, } else { got_packet = 1; } -#else -#if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( codec_context, pkt, opicture_ptr, &got_packet ); - if ( ret != 0 ) { - Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); - } -#else - int out_size = avcodec_encode_video( codec_context, video_outbuf, video_outbuf_size, opicture_ptr ); - got_packet = out_size > 0 ? 1 : 0; - pkt->data = got_packet ? video_outbuf : nullptr; - pkt->size = got_packet ? out_size : 0; -#endif -#endif if ( got_packet ) { // if ( c->coded_frame->key_frame ) // { -//#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) // pkt->flags |= AV_PKT_FLAG_KEY; -//#else -// pkt->flags |= PKT_FLAG_KEY; -//#endif // } if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { @@ -691,43 +527,33 @@ int VideoStream::SendPacket(AVPacket *packet) { if ( ret != 0 ) { Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) av_packet_unref( packet ); -#else - av_free_packet( packet ); -#endif return ret; } -void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - - if (ctx == nullptr) return nullptr; +void *VideoStream::StreamingThreadCallback(void *ctx) { + Debug(1, "StreamingThreadCallback started"); - VideoStream* videoStream = reinterpret_cast(ctx); + if (ctx == nullptr) { + return nullptr; + } - const uint64_t nanosecond_multiplier = 1000000000; + VideoStream *videoStream = reinterpret_cast(ctx); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); -#else - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); -#endif - uint64_t frame_count = 0; - timespec start_time; - clock_gettime(CLOCK_MONOTONIC, &start_time); - uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; - while(videoStream->do_streaming) { - timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; - uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); - - if ( current_time_ns < target_ns ) { - // It's not time to render a frame yet. - usleep( (target_ns - current_time_ns) * 0.001 ); - } + TimePoint::duration target_interval = std::chrono::duration_cast(FPSeconds( + videoStream->codec_context->time_base.num / static_cast(videoStream->codec_context->time_base.den))); + + uint64_t frame_count = 0; + TimePoint start_time = std::chrono::steady_clock::now(); + + while (videoStream->do_streaming) { + TimePoint current_time = std::chrono::steady_clock::now(); + TimePoint target = start_time + (target_interval * frame_count); + + if (current_time < target) { + // It's not time to render a frame yet. + std::this_thread::sleep_for(target - current_time); + } // By sending the last rendered frame we deliver frames to the client more accurate. // If we're encoding the frame before sending it there will be lag. @@ -738,32 +564,29 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ if (packet->size) { videoStream->SendPacket(packet); } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( packet); -#else - av_free_packet( packet ); -#endif + av_packet_unref(packet); + videoStream->packet_index = videoStream->packet_index ? 0 : 1; // Lock buffer and render next frame. - - if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { - Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); - } - - if ( videoStream->buffer_copy ) { - // Encode next frame. - videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); - } - - if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { - Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); - } - - frame_count++; - } - - return nullptr; -} + if (pthread_mutex_lock(videoStream->buffer_copy_lock) != 0) { + Fatal("StreamingThreadCallback: pthread_mutex_lock failed."); + } -#endif // HAVE_LIBAVCODEC + if (videoStream->buffer_copy) { + // Encode next frame. + videoStream->ActuallyEncodeFrame(videoStream->buffer_copy, + videoStream->buffer_copy_used, + videoStream->add_timestamp, + videoStream->timestamp); + } + + if (pthread_mutex_unlock(videoStream->buffer_copy_lock) != 0) { + Fatal("StreamingThreadCallback: pthread_mutex_unlock failed."); + } + + frame_count++; + } + + return nullptr; +} diff --git a/src/zm_mpeg.h b/src/zm_mpeg.h index 4999f1328..b6a6f49be 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -23,8 +23,6 @@ #include "zm_ffmpeg.h" #include -#if HAVE_LIBAVCODEC - class VideoStream { protected: struct MimeData { @@ -83,6 +81,4 @@ public: double EncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp=false, unsigned int timestamp=0 ); }; -#endif // HAVE_LIBAVCODEC - #endif // ZM_MPEG_H diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 133747ea2..55a78721e 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -21,23 +21,44 @@ #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() : 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) + codec_imgsize(0), + pts(0), + decoded(false) +{ + av_init_packet(&packet); + packet.size = 0; // So we can detect whether it has been filled. +} + +ZMPacket::ZMPacket(Image *i, SystemTimePoint tv) : + keyframe(0), + stream(nullptr), + in_frame(nullptr), + out_frame(nullptr), + timestamp(tv), + buffer(nullptr), + image(i), + analysis_image(nullptr), + score(-1), + codec_type(AVMEDIA_TYPE_UNKNOWN), + image_index(-1), + codec_imgsize(0), + pts(0), + decoded(false) { av_init_packet(&packet); packet.size = 0; // So we can detect whether it has been filled. @@ -45,94 +66,35 @@ ZMPacket::ZMPacket() : ZMPacket::ZMPacket(ZMPacket &p) : keyframe(0), + stream(nullptr), in_frame(nullptr), out_frame(nullptr), - timestamp(nullptr), + timestamp(p.timestamp), buffer(nullptr), image(nullptr), analysis_image(nullptr), score(-1), codec_type(AVMEDIA_TYPE_UNKNOWN), image_index(-1), - codec_imgsize(0) + codec_imgsize(0), + pts(0), + decoded(false) { av_init_packet(&packet); packet.size = 0; packet.data = nullptr; - if ( zm_av_packet_ref(&packet, &p.packet) < 0 ) { + if (zm_av_packet_ref(&packet, &p.packet) < 0) { Error("error refing packet"); } - timestamp = new struct timeval; - *timestamp = *p.timestamp; } ZMPacket::~ZMPacket() { 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; - analysis_image = nullptr; - } - if ( image ) { - delete image; - image = nullptr; - } - if ( timestamp ) { - delete timestamp; - timestamp = nullptr; - } - -#if 0 - if ( image ) { - if ( image->IsBufferHeld() ) { - // Don't free the mmap'd image - } else { - delete image; - image = nullptr; - delete timestamp; - timestamp = nullptr; - } - } else { - if ( timestamp ) { - delete timestamp; - timestamp = nullptr; - } - } -#endif -} - -// deprecated -void ZMPacket::reset() { - 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; - analysis_image = nullptr; - } -#if 0 - if ( (! image) && timestamp ) { - delete timestamp; - timestamp = NULL; - } -#endif - score = -1; - keyframe = 0; + if (in_frame) av_frame_free(&in_frame); + if (out_frame) av_frame_free(&out_frame); + if (buffer) av_freep(&buffer); + delete analysis_image; + delete image; } /* returns < 0 on error, 0 on not ready, int bytes consumed on success @@ -143,15 +105,18 @@ void ZMPacket::reset() { int ZMPacket::decode(AVCodecContext *ctx) { Debug(4, "about to decode video, image_index is (%d)", image_index); - if ( in_frame ) { + 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 ) { + if (ret < 0) { + if (AVERROR(EAGAIN) != ret) { Warning("Unable to receive frame : code %d %s.", ret, av_make_error_string(ret).c_str()); } @@ -159,14 +124,14 @@ int ZMPacket::decode(AVCodecContext *ctx) { return 0; } int bytes_consumed = ret; - if ( ret > 0 ) { + 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.", + if (fix_deprecated_pix_fmt(ctx->sw_pix_fmt) != fix_deprecated_pix_fmt(static_cast(in_frame->format))) { + Debug(3, "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)) @@ -213,7 +178,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { /* 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 ) { + if (ret < 0) { Error("Unable to transfer frame: %s, continuing", av_make_error_string(ret).c_str()); av_frame_free(&in_frame); @@ -221,7 +186,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { return 0; } ret = av_frame_copy_props(new_frame, in_frame); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy props: %s, continuing", av_make_error_string(ret).c_str()); } @@ -238,7 +203,7 @@ int ZMPacket::decode(AVCodecContext *ctx) { } else #endif #endif - Debug(2, "Same pix format %s so not hwtransferring. sw_pix_fmt is %s", + Debug(3, "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) ); @@ -252,12 +217,12 @@ int ZMPacket::decode(AVCodecContext *ctx) { } // end ZMPacket::decode Image *ZMPacket::get_image(Image *i) { - if ( !in_frame ) { + if (!in_frame) { Error("Can't get image without frame.. maybe need to decode first"); return nullptr; } - if ( !image ) { - if ( !i ) { + if (!image) { + if (!i) { Error("Need a pre-allocated image buffer"); return nullptr; } @@ -273,54 +238,47 @@ Image *ZMPacket::set_image(Image *i) { } AVPacket *ZMPacket::set_packet(AVPacket *p) { - if ( zm_av_packet_ref(&packet, p) < 0 ) { + if (zm_av_packet_ref(&packet, p) < 0) { Error("error refing packet"); } - //ZM_DUMP_PACKET(packet, "zmpacket:"); - gettimeofday(timestamp, nullptr); + + timestamp = std::chrono::system_clock::now(); keyframe = p->flags & AV_PKT_FLAG_KEY; return &packet; } -AVFrame *ZMPacket::get_out_frame(const AVCodecContext *ctx) { - if ( !out_frame ) { +AVFrame *ZMPacket::get_out_frame(int width, int height, AVPixelFormat format) { + if (!out_frame) { out_frame = zm_av_frame_alloc(); - if ( !out_frame ) { + if (!out_frame) { Error("Unable to allocate a frame"); return nullptr; } -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + int alignment = 32; + if (width%alignment) alignment = 1; + codec_imgsize = av_image_get_buffer_size( - ctx->pix_fmt, - ctx->width, - ctx->height, 32); + format, width, height, alignment); + 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); - av_image_fill_arrays( + int ret; + if ((ret=av_image_fill_arrays( out_frame->data, out_frame->linesize, buffer, - ctx->pix_fmt, - ctx->width, - ctx->height, - 32); -#else - codec_imgsize = avpicture_get_size( - ctx->pix_fmt, - ctx->width, - ctx->height); - buffer = (uint8_t *)av_malloc(codec_imgsize); - avpicture_fill( - (AVPicture *)out_frame, - buffer, - ctx->pix_fmt, - ctx->width, - ctx->height - ); -#endif - out_frame->width = ctx->width; - out_frame->height = ctx->height; - out_frame->format = ctx->pix_fmt; + format, + width, + height, + alignment))<0) { + Error("Failed to fill_arrays %s", av_make_error_string(ret).c_str()); + av_frame_free(&out_frame); + return nullptr; + } + + 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 ea609429f..9cce4dd03 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -21,27 +21,32 @@ #define ZM_PACKET_H #include "zm_logger.h" +#include "zm_time.h" +#include "zm_zone.h" + +#include #include +#include extern "C" { #include } -#ifdef __FreeBSD__ -#include -#endif // __FreeBSD__ - class Image; class ZMPacket { public: - std::recursive_mutex mutex; + std::mutex mutex_; + // The condition has to be in the packet because it is shared between locks + 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; + SystemTimePoint timestamp; uint8_t *buffer; // buffer used in image Image *image; Image *analysis_image; @@ -49,6 +54,9 @@ class ZMPacket { 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; } @@ -58,27 +66,57 @@ class ZMPacket { Image *set_image(Image *); int is_keyframe() { return keyframe; }; - int decode( AVCodecContext *ctx ); - void reset(); - explicit ZMPacket(Image *image); + int decode(AVCodecContext *ctx); + explicit ZMPacket(Image *image, SystemTimePoint tv); explicit ZMPacket(ZMPacket &packet); ZMPacket(); ~ZMPacket(); - void lock() { - Debug(4,"Locking packet %d", this->image_index); - mutex.lock(); - Debug(4,"packet %d locked", this->image_index); - }; - bool trylock() { - Debug(4,"TryLocking packet %d", this->image_index); - return mutex.try_lock(); - }; - void unlock() { - Debug(4,"packet %d unlocked", this->image_index); - mutex.unlock(); - }; - AVFrame *get_out_frame( const AVCodecContext *ctx ); + + //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: + std::shared_ptr packet_; + std::unique_lock lck_; + bool locked; + + explicit ZMLockedPacket(std::shared_ptr 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 c89347692..509a25ee6 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -24,40 +24,44 @@ #include "zm_ffmpeg.h" #include "zm_packet.h" #include "zm_signal.h" -#include 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) + deleting(false), + keep_keyframes(false) { } /* Assumes queue is empty when adding streams * Assumes first stream added will be the video stream */ -void PacketQueue::addStreamId(int p_stream_id) { +int PacketQueue::addStream() { deleting = false; - if ( video_stream_id == -1 ) - video_stream_id = p_stream_id; - if ( max_stream_id < p_stream_id ) { - if ( packet_counts ) delete[] packet_counts; - max_stream_id = p_stream_id; - packet_counts = new int[max_stream_id+1]; - for ( int i=0; i <= max_stream_id; ++i ) - packet_counts[i] = 0; + 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) + packet_counts[i] = 0; + return max_stream_id; } PacketQueue::~PacketQueue() { clear(); - if ( packet_counts ) { + if (packet_counts) { delete[] packet_counts; packet_counts = nullptr; } - while ( !iterators.empty() ) { + while (!iterators.empty()) { packetqueue_iterator *it = iterators.front(); iterators.pop_front(); delete it; @@ -70,8 +74,7 @@ PacketQueue::~PacketQueue() { * Thus it will ensure that the same packet never gets queued twice. */ -bool PacketQueue::queuePacket(ZMPacket* add_packet) { - Debug(4, "packetqueue queuepacket %p %d", add_packet, add_packet->image_index); +bool PacketQueue::queuePacket(std::shared_ptr add_packet) { if (iterators.empty()) { Debug(4, "No iterators so no one needs us to queue packets."); return false; @@ -80,34 +83,91 @@ bool PacketQueue::queuePacket(ZMPacket* add_packet) { Debug(4, "No video keyframe so no one needs us to queue packets."); return false; } - mutex.lock(); + { + std::unique_lock lck(mutex); - pktQueue.push_back(add_packet); - packet_counts[add_packet->packet.stream_index] += 1; - Debug(1, "packet counts for %d is %d", - add_packet->packet.stream_index, - packet_counts[add_packet->packet.stream_index]); + 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 + for ( + auto iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + packetqueue_iterator *iterator_it = *iterators_it; + if (*iterator_it == pktQueue.end()) { + --(*iterator_it); + } + } // end foreach iterator + + if ( + (add_packet->packet.stream_index == video_stream_id) + and + (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." + , max_video_packet_count); + + for ( + auto it = ++pktQueue.begin(); + it != pktQueue.end() and *it != add_packet; ) { - packetqueue_iterator *iterator_it = *iterators_it; - if ( *iterator_it == pktQueue.end() ) { - Debug(4, "pointing it %p to back", iterator_it); - --(*iterator_it); - } - } // end foreach iterator - mutex.unlock(); + std::shared_ptr zm_packet = *it; + + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) { + Debug(1, "Found locked packet when trying to free up video packets. Skipping to next one"); + delete lp; + ++it; + continue; + } + + for ( + auto iterators_it = iterators.begin(); + iterators_it != iterators.end(); + ++iterators_it + ) { + auto 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!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) { + Debug(1, "Bumping IT because it is at the front that we are deleting"); + ++(*iterator_it); + } + } // end foreach iterator + + it = pktQueue.erase(it); + 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 lp; + + if (zm_packet->packet.stream_index == video_stream_id) + break; + } // end while + } // end if not able catch up + } // 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) +} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) -void PacketQueue::clearPackets(ZMPacket *add_packet) { +void PacketQueue::clearPackets(const std::shared_ptr &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 @@ -117,68 +177,142 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { // // 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 ( ! ( + if (deleting) return; + if (!pktQueue.size()) return; + + if (keep_keyframes and ! ( add_packet->packet.stream_index == video_stream_id - and - add_packet->keyframe - and - (packet_counts[video_stream_id] > max_video_packet_count) - and - *(pktQueue.begin()) != add_packet - ) + 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 ) + ); return; } std::unique_lock lck(mutex); - packetqueue_iterator it = pktQueue.begin(); - packetqueue_iterator next_front = pktQueue.begin(); + // 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 %zu", tail_count, pktQueue.size()); - // First packet is special because we know it is a video keyframe and only need to check for lock - ZMPacket *zm_packet = *it; - if ( zm_packet->trylock() ) { - ++it; - zm_packet->unlock(); + 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)) { + std::shared_ptr zm_packet = *pktQueue.begin(); + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (!lp->trylock()) break; + delete lp; - // 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; - if ( !zm_packet->trylock() ) { - break; - } - zm_packet->unlock(); - - if ( is_there_an_iterator_pointing_to_packet(zm_packet) ) { + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { 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(1, "Have a video keyframe so setting next front to it"); + 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; + } + + auto it = pktQueue.begin(); + auto next_front = pktQueue.begin(); + + // First packet is special because we know it is a video keyframe and only need to check for lock + std::shared_ptr zm_packet = *it; + if (zm_packet == add_packet) { + return; + } + + ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); + if (lp->trylock()) { + int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking + Debug(4, "Have lock on first packet"); + ++it; + delete lp; + + // 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()) { + Debug(3, "Failed locking packet %d", zm_packet->image_index); + delete lp; + break; + } + delete lp; + +#if 0 + // There are no threads that follow analysis thread. So there cannot be an it pointing here + if (is_there_an_iterator_pointing_to_packet(zm_packet)) { + if (pktQueue.begin() == next_front) + Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); + break; + } +#endif + + 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(3, "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++; + ++it; } // end while } // end if first packet not locked - Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d", + Debug(1, "Resulting it 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 ) { + if (next_front != pktQueue.begin()) { + while (pktQueue.begin() != next_front) { + zm_packet = *pktQueue.begin(); + if (!zm_packet) { Error("NULL zm_packet in queue"); continue; } - Debug(1, "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%d", - zm_packet->packet.stream_index, zm_packet->image_index, zm_packet->keyframe, packet_counts[video_stream_id], max_video_packet_count, pktQueue.size()); + 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; + //delete zm_packet; } } // end if have at least max_video_packet_count video packets remaining // We signal on every packet because someday we may analyze sound @@ -186,137 +320,32 @@ void PacketQueue::clearPackets(ZMPacket *add_packet) { return; } // end voidPacketQueue::clearPackets(ZMPacket* zm_packet) -ZMPacket* PacketQueue::popPacket( ) { - Debug(4, "pktQueue size %d", pktQueue.size()); - if ( pktQueue.empty() ) { - return nullptr; - } - Debug(4, "poPacket Mutex locking"); - mutex.lock(); - - 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 - - zm_packet->lock(); - - pktQueue.pop_front(); - packet_counts[zm_packet->packet.stream_index] -= 1; - - mutex.unlock(); - - return zm_packet; -} // popPacket - - -/* 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 %d", frames_to_keep, pktQueue.size()); - - if ( pktQueue.empty() ) { - return 0; - } - - // 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"); - mutex.lock(); - - 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(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 ) { - frames_to_keep --; - } - it --; - } - - // 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; - } - - int delete_count = 0; - - // 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 %d", - 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(); - //if ( zm_packet->image_index == -1 ) - delete zm_packet; - - delete_count += 1; - } // while our iterator is not the first packet - zm_packet = nullptr; // tidy up for valgrind - Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size()); - mutex.unlock(); - return delete_count; - - Debug(3, "Deleted packets, resulting size is %d", pktQueue.size()); - mutex.unlock(); - return delete_count; -} // end unsigned int PacketQueue::clear( unsigned int frames_to_keep, int stream_id ) - void PacketQueue::clear() { deleting = true; - + condition.notify_all(); + if (!packet_counts) // special case, not initialised + return; + Debug(1, "Clearing packetqueue"); std::unique_lock lck(mutex); - while ( !pktQueue.empty() ) { - ZMPacket *packet = pktQueue.front(); + while (!pktQueue.empty()) { + std::shared_ptr 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 - packet->lock(); - packet_counts[packet->packet.stream_index] -= 1; + ZMLockedPacket *lp = new ZMLockedPacket(packet); + lp->lock(); + Debug(1, + "Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu", + packet->packet.stream_index, + packet->image_index, + packet->keyframe, + packet_counts[video_stream_id], + pre_event_video_packet_count, + pktQueue.size()); pktQueue.pop_front(); - packet->unlock(); - delete packet; + delete lp; + //delete packet; } + Debug(1, "Packetqueue is clear, deleting iterators"); for ( std::list::iterator iterators_it = iterators.begin(); @@ -326,165 +355,144 @@ void PacketQueue::clear() { 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; + + Debug(1, "Packetqueue is clear, notifying"); condition.notify_all(); } -// clear queue keeping only specified duration of video -- return number of pkts removed -unsigned int PacketQueue::clear(struct timeval *duration, int streamId) { - - if ( pktQueue.empty() ) { - return 0; - } - Debug(4, "Locking in clear"); - mutex.lock(); - - 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", - streamId, pktQueue.size()); - for ( ; it != pktQueue.rend(); ++it) { - ZMPacket *zm_packet = *it; - AVPacket *av_packet = &(zm_packet->packet); - 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 %d.%d", - av_packet->stream_index, - zm_packet->timestamp->tv_sec, - zm_packet->timestamp->tv_usec); - break; - } - } - - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a frame before queue preserve time. keeping all"); - mutex.unlock(); - return 0; - } - - 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) - and - (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 ); - break; - } - } - if ( it == pktQueue.rend() ) { - Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); - mutex.unlock(); - return 0; - } - - unsigned int deleted_frames = 0; - ZMPacket *zm_packet = nullptr; - 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; - //if ( zm_packet->image_index == -1 ) - delete zm_packet; - deleted_frames += 1; - } - zm_packet = nullptr; - Debug(3, "Deleted %d frames", deleted_frames); - mutex.unlock(); - - return deleted_frames; -} - unsigned int PacketQueue::size() { return pktQueue.size(); } int PacketQueue::packet_count(int stream_id) { - if ( stream_id < 0 or stream_id > max_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 PacketQueue::packet_count(int stream_id) +// Returns a packet. Packet will be locked +ZMLockedPacket *PacketQueue::get_packet(packetqueue_iterator *it) { + if (deleting or zm_terminate) + return nullptr; + + Debug(4, "Locking in get_packet using it %p queue end? %d", + std::addressof(*it), (*it == pktQueue.end())); + + ZMLockedPacket *lp = nullptr; + { // scope for lock + std::unique_lock lck(mutex); + Debug(4, "Have Lock in get_packet"); + while (!lp) { + while ((*it == pktQueue.end()) and !(deleting or zm_terminate)) { + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } + if (deleting or zm_terminate) break; + + std::shared_ptr p = *(*it); + if (!p) { + Error("Null p?!"); + return nullptr; + } + Debug(3, "get_packet using it %p locking index %d", + std::addressof(*it), p->image_index); + + 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 + } // end scope for lock + + if (!lp) { + Debug(1, "terminated, leaving"); + condition.notify_all(); + } + return lp; +} // end ZMLockedPacket *PacketQueue::get_packet(it) // Returns a packet. Packet will be locked -ZMPacket *PacketQueue::get_packet(packetqueue_iterator *it) { - if ( deleting or zm_terminate ) +ZMLockedPacket *PacketQueue::get_packet_and_increment_it(packetqueue_iterator *it) { + if (deleting or zm_terminate) return nullptr; - Debug(4, "Locking in get_packet using it %p queue end? %d, packet %p", - *it, (*it == pktQueue.end()), *(*it)); - std::unique_lock lck(mutex); - Debug(4, "Have Lock in get_packet"); + Debug(4, "Locking in get_packet using it %p queue end? %d", + std::addressof(*it), (*it == pktQueue.end())); - while ( (!pktQueue.size()) or (*it == pktQueue.end()) ) { - if ( deleting or zm_terminate ) - return nullptr; - Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), (*it == pktQueue.end())); - condition.wait(lck); - } - if ( deleting or zm_terminate ) - return nullptr; + ZMLockedPacket *lp = nullptr; + { // scope for lock + std::unique_lock lck(mutex); + Debug(4, "Have Lock in get_packet"); + while (!lp) { + while ((*it == pktQueue.end()) and !(deleting or zm_terminate)) { + Debug(2, "waiting. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); + condition.wait(lck); + } + if (deleting or zm_terminate) break; - Debug(4, "get_packet using it %p queue end? %d, packet %p", - *it, (*it == pktQueue.end()), *(*it)); - ZMPacket *p = *(*it); - if ( !p ) { - Error("Null p?!"); - return nullptr; + std::shared_ptr p = *(*it); + if (!p) { + Error("Null p?!"); + return nullptr; + } + Debug(3, "get_packet using it %p locking index %d", + std::addressof(*it), p->image_index); + + lp = new ZMLockedPacket(p); + if (lp->trylock()) { + Debug(2, "Locked packet %d, unlocking packetqueue mutex, incrementing it", p->image_index); + ++(*it); + 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 + } // end scope for lock + + if (!lp) { + Debug(1, "terminated, leaving"); + condition.notify_all(); } - Debug(3, "get_packet %p image_index: %d, about to lock packet", p, p->image_index); - while ( !(zm_terminate or deleting) and !p->trylock() ) { - Debug(3, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) ); - condition.wait(lck); - } - Debug(2, "Locked packet, unlocking packetqueue mutex"); - return p; -} // end ZMPacket *PacketQueue::get_packet(it) + return lp; +} // end ZMLockedPacket *PacketQueue::get_packet_and_increment_it(it) + +void PacketQueue::unlock(ZMLockedPacket *lp) { + delete lp; + condition.notify_all(); +} bool PacketQueue::increment_it(packetqueue_iterator *it) { - Debug(2, "Incrementing %p, queue size %d, end? %d", it, pktQueue.size(), ((*it) == pktQueue.end())); - if ( (*it) == pktQueue.end() ) { + 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 %p, so returning true", it, *it, pktQueue.end()); + 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 %d, end? %d", it, pktQueue.size(), (*it == pktQueue.end())); - if ( *it == pktQueue.end() ) { + Debug(2, "Incrementing %p, queue size %zu, end? %d", it, pktQueue.size(), (*it == pktQueue.end())); + if (*it == pktQueue.end()) { return false; } @@ -504,67 +512,65 @@ 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; - ZM_DUMP_PACKET((*(*it))->packet, ""); + std::shared_ptr 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 - while ( ( (*it) != pktQueue.begin() ) and pre_event_count ) { - Debug(1, "Previous packet pre_event_count %d stream_index %d keyframe %d", - pre_event_count, (*(*it))->packet.stream_index, (*(*it))->keyframe); - ZM_DUMP_PACKET((*(*it))->packet, ""); - if ( (*(*it))->packet.stream_index == video_stream_id ) { - pre_event_count --; - if ( ! pre_event_count ) - break; + 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)--; } - (*it)--; + packet = *(*it); } // it either points to beginning or we have seen pre_event_count video packets. - if ( (*it) == pktQueue.begin() ) { - Debug(1, "Hit begin"); - // hit end, the first packet in the queue should ALWAYS be a video keyframe. - // So we should be able to return it. - if ( pre_event_count ) { - if ( (*(*it))->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); - } - ZM_DUMP_PACKET((*(*it))->packet, ""); + 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); } + ZM_DUMP_PACKET(packet->packet, ""); + return it; + } else if (!keep_keyframes) { + // Are encoding, so don't care about keyframes return it; } - // Not at beginning, so must be pointing at a video keyframe or maybe pre_event_count == 0 - if ( (*(*it))->keyframe ) { - ZM_DUMP_PACKET((*(*it))->packet, "Found video keyframe, Returning"); - return it; - } - - while ( --(*it) != pktQueue.begin() ) { - ZM_DUMP_PACKET((*(*it))->packet, "No keyframe"); - if ( ((*(*it))->packet.stream_index == video_stream_id) and (*(*it))->keyframe ) + 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); } - if ( !(*(*it))->keyframe ) { - Warning("Hit end of packetqueue before satisfying pre_event_count. Needed %d more video frames", pre_event_count); + if (!(*(*it))->keyframe) { + Warning("Hit beginning of packetqueue and packet is not a keyframe. index is %d", packet->image_index); } return it; } // end packetqueue_iterator *PacketQueue::get_event_start_packet_it void PacketQueue::dumpQueue() { - std::list::reverse_iterator it; + std::list>::reverse_iterator it; for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { - ZMPacket *zm_packet = *it; + std::shared_ptr zm_packet = *it; ZM_DUMP_PACKET(zm_packet->packet, ""); } } @@ -581,7 +587,7 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { if ( wait ) { while ( ((! pktQueue.size()) or (*it == pktQueue.end())) and !zm_terminate and !deleting ) { - Debug(2, "waiting. Queue size %d it == end? %d", pktQueue.size(), ( *it == pktQueue.end() ) ); + Debug(2, "waiting for packets in queue. Queue size %zu it == end? %d", pktQueue.size(), (*it == pktQueue.end())); condition.wait(lck); *it = pktQueue.begin(); } @@ -592,16 +598,16 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { } } - while ( *it != pktQueue.end() ) { - ZMPacket *zm_packet = *(*it); - if ( !zm_packet ) { + while (*it != pktQueue.end()) { + std::shared_ptr 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 ) ) { + 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; } @@ -609,7 +615,7 @@ packetqueue_iterator * PacketQueue::get_video_it(bool wait) { } 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 ( @@ -624,28 +630,44 @@ void PacketQueue::free_it(packetqueue_iterator *it) { } } -bool PacketQueue::is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet) { +bool PacketQueue::is_there_an_iterator_pointing_to_packet(const std::shared_ptr &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() ) { + if (*iterator_it == pktQueue.end()) { continue; } - Debug(4, "Checking iterator %p == packet ? %d", (*iterator_it), ( *(*iterator_it) == zm_packet )); + 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 ) { + 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 < 1 ) - max_video_packet_count = 1 ; - // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue - } + +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 +} + +void PacketQueue::notify_all() { + condition.notify_all(); +}; + +void PacketQueue::wait() { + std::unique_lock lck(mutex); + condition.wait(lck); +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index cd88648b9..20cfdfe85 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -22,21 +22,26 @@ #include #include #include +#include class ZMPacket; +class ZMLockedPacket; -typedef std::list::iterator packetqueue_iterator; +typedef std::list>::iterator packetqueue_iterator; class PacketQueue { public: // For now just to ease development - std::list pktQueue; - std::list::iterator analysis_it; + 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; @@ -45,30 +50,27 @@ class PacketQueue { public: PacketQueue(); virtual ~PacketQueue(); - std::list::const_iterator end() const { return pktQueue.end(); } - std::list::const_iterator begin() const { return pktQueue.begin(); } + std::list>::const_iterator end() const { return pktQueue.end(); } + std::list>::const_iterator begin() const { return pktQueue.begin(); } - void addStreamId(int p_stream_id); + int addStream(); void setMaxVideoPackets(int p); + void setPreEventVideoPackets(int p); + void setKeepKeyframes(bool k) { keep_keyframes = k; }; - bool queuePacket(ZMPacket* packet); - ZMPacket * 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); + bool queuePacket(std::shared_ptr packet); 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 *); + void clearPackets(const std::shared_ptr &packet); int packet_count(int stream_id); bool increment_it(packetqueue_iterator *it); bool increment_it(packetqueue_iterator *it, int stream_id); - ZMPacket *get_packet(packetqueue_iterator *); + ZMLockedPacket *get_packet(packetqueue_iterator *); + ZMLockedPacket *get_packet_and_increment_it(packetqueue_iterator *); packetqueue_iterator *get_video_it(bool wait); packetqueue_iterator *get_stream_it(int stream_id); void free_it(packetqueue_iterator *); @@ -77,7 +79,10 @@ class PacketQueue { packetqueue_iterator snapshot_it, unsigned int pre_event_count ); - bool is_there_an_iterator_pointing_to_packet(ZMPacket *zm_packet); + bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr &zm_packet); + void unlock(ZMLockedPacket *lp); + void notify_all(); + void wait(); }; #endif /* ZM_PACKETQUEUE_H */ diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index f439b9491..6485bc192 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -19,91 +19,103 @@ #include "zm_poly.h" +#include "zm_line.h" #include -void Polygon::calcArea() { - double float_area = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - double trap_area = ((coords[i].X()-coords[j].X())*((coords[i].Y()+coords[j].Y())))/2.0L; +Polygon::Polygon(std::vector vertices) : vertices_(std::move(vertices)), area(0) { + UpdateExtent(); + UpdateArea(); + UpdateCentre(); +} + +void Polygon::UpdateExtent() { + if (vertices_.empty()) + return; + + int min_x = vertices_[0].x_; + int max_x = 0; + int min_y = vertices_[0].y_; + int max_y = 0; + for (const Vector2 &vertex : vertices_) { + min_x = std::min(min_x, vertex.x_); + max_x = std::max(max_x, vertex.x_); + min_y = std::min(min_y, vertex.y_); + max_y = std::max(max_y, vertex.y_); + } + + extent = Box({min_x, min_y}, {max_x, max_y}); +} + +void Polygon::UpdateArea() { + double float_area = 0.0; + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + double trap_area = ((vertices_[i].x_ - vertices_[j].x_) * ((vertices_[i].y_ + vertices_[j].y_))) / 2.0; float_area += trap_area; - //printf( "%.2f (%.2f)\n", float_area, trap_area ); } - area = (int)round(fabs(float_area)); + + area = static_cast(std::lround(std::fabs(float_area))); } -void Polygon::calcCentre() { - if ( !area && n_coords ) - calcArea(); - double float_x = 0.0L, float_y = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - float_x += ((coords[i].Y()-coords[j].Y())*((coords[i].X()*2)+(coords[i].X()*coords[j].X())+(coords[j].X()*2))); - float_y += ((coords[j].X()-coords[i].X())*((coords[i].Y()*2)+(coords[i].Y()*coords[j].Y())+(coords[j].Y()*2))); +void Polygon::UpdateCentre() { + if (!area && !vertices_.empty()) + UpdateArea(); + + double float_x = 0.0; + double float_y = 0.0; + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + float_x += ((vertices_[i].y_ - vertices_[j].y_) + * ((vertices_[i].x_ * 2) + (vertices_[i].x_ * vertices_[j].x_) + (vertices_[j].x_ * 2))); + float_y += ((vertices_[j].x_ - vertices_[i].x_) + * ((vertices_[i].y_ * 2) + (vertices_[i].y_ * vertices_[j].y_) + (vertices_[j].y_ * 2))); } - 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) ); + float_x /= (6 * area); + float_y /= (6 * area); + + centre = Vector2(static_cast(std::lround(float_x)), static_cast(std::lround(float_y))); } -Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords( p_n_coords ) { - coords = new Coord[n_coords]; - - int min_x = -1; - int max_x = -1; - int min_y = -1; - int max_y = -1; - for ( int i = 0; i < n_coords; i++ ) { - coords[i] = p_coords[i]; - if ( min_x == -1 || coords[i].X() < min_x ) - min_x = coords[i].X(); - if ( max_x == -1 || coords[i].X() > max_x ) - max_x = coords[i].X(); - if ( min_y == -1 || coords[i].Y() < min_y ) - min_y = coords[i].Y(); - if ( max_y == -1 || coords[i].Y() > max_y ) - max_y = coords[i].Y(); - } - extent = Box( min_x, min_y, max_x, max_y ); - calcArea(); - calcCentre(); -} - -Polygon::Polygon( const Polygon &p_polygon ) : - n_coords(p_polygon.n_coords), - extent(p_polygon.extent), - area(p_polygon.area), - centre(p_polygon.centre) -{ - coords = new Coord[n_coords]; - for( int i = 0; i < n_coords; i++ ) { - coords[i] = p_polygon.coords[i]; - } -} - -Polygon &Polygon::operator=( const Polygon &p_polygon ) { - if ( n_coords < p_polygon.n_coords ) { - delete[] coords; - coords = new Coord[p_polygon.n_coords]; - } - n_coords = p_polygon.n_coords; - for ( int i = 0; i < n_coords; i++ ) { - coords[i] = p_polygon.coords[i]; - } - extent = p_polygon.extent; - area = p_polygon.area; - centre = p_polygon.centre; - return *this ; -} - -bool Polygon::isInside( const Coord &coord ) const { +bool Polygon::Contains(const Vector2 &coord) const { bool inside = false; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { - if ( (((coords[i].Y() <= coord.Y()) && (coord.Y() < coords[j].Y()) ) - || ((coords[j].Y() <= coord.Y()) && (coord.Y() < coords[i].Y()))) - && (coord.X() < (coords[j].X() - coords[i].X()) * (coord.Y() - coords[i].Y()) / (coords[j].Y() - coords[i].Y()) + coords[i].X())) - { + for (size_t i = 0, j = vertices_.size() - 1; i < vertices_.size(); j = i++) { + if ((((vertices_[i].y_ <= coord.y_) && (coord.y_ < vertices_[j].y_)) || ((vertices_[j].y_ <= coord.y_) && (coord.y_ < vertices_[i].y_))) + && (coord.x_ < (vertices_[j].x_ - vertices_[i].x_) * (coord.y_ - vertices_[i].y_) / (vertices_[j].y_ - vertices_[i].y_) + vertices_[i].x_)) { inside = !inside; } } return inside; } + +// Clip the polygon to a rectangular boundary box using the Sutherland-Hodgman algorithm +void Polygon::Clip(const Box &boundary) { + std::vector clipped_vertices = vertices_; + + for (LineSegment const &clip_edge : boundary.Edges()) { + // convert our line segment to an infinite line + Line clip_line = Line(clip_edge); + + std::vector to_clip = clipped_vertices; + clipped_vertices.clear(); + + for (size_t i = 0; i < to_clip.size(); ++i) { + Vector2 vert1 = to_clip[i]; + Vector2 vert2 = to_clip[(i + 1) % to_clip.size()]; + + bool vert1_left = clip_line.IsPointLeftOfOrColinear(vert1); + bool vert2_left = clip_line.IsPointLeftOfOrColinear(vert2); + + if (vert2_left) { + if (!vert1_left) { + clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line)); + } + clipped_vertices.push_back(vert2); + } else if (vert1_left) { + clipped_vertices.push_back(Line(vert1, vert2).Intersection(clip_line)); + } + } + } + + vertices_ = clipped_vertices; + UpdateExtent(); + UpdateArea(); + UpdateCentre(); +} diff --git a/src/zm_poly.h b/src/zm_poly.h index e7d2581b7..c25033262 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -21,98 +21,36 @@ #define ZM_POLY_H #include "zm_box.h" +#include -class Coord; - -// -// Class used for storing a box, which is defined as a region -// defined by two coordinates -// +// This class represents convex or concave non-self-intersecting polygons. class Polygon { -protected: - struct Edge { - int min_y; - int max_y; - double min_x; - double _1_m; + public: + Polygon() : area(0) {} + explicit Polygon(std::vector vertices); - 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); - } - }; + const std::vector &GetVertices() const { + return vertices_; + } - struct Slice { - int min_x; - int max_x; - int n_edges; - int *edges; + const Box &Extent() const { return extent; } + int32 Area() const { return area; } + const Vector2 &Centre() const { return centre; } - Slice() { - min_x = 0; - max_x = 0; - n_edges = 0; - edges = nullptr; - } - ~Slice() { - delete edges; - } - }; + bool Contains(const Vector2 &coord) const; -protected: - int n_coords; - Coord *coords; + void Clip(const Box &boundary); + + private: + void UpdateExtent(); + void UpdateArea(); + void UpdateCentre(); + + private: + std::vector vertices_; Box extent; - int area; - Coord centre; - Edge *edges; - Slice *slices; - -protected: - void initialiseEdges(); - void calcArea(); - void calcCentre(); - -public: - inline Polygon() : n_coords(0), coords(nullptr), area(0), edges(nullptr), slices(nullptr) { - } - Polygon(int p_n_coords, const Coord *p_coords); - Polygon(const Polygon &p_polygon); - ~Polygon() { - delete[] coords; - } - - Polygon &operator=( const Polygon &p_polygon ); - - inline int getNumCoords() const { return n_coords; } - inline const Coord &getCoord( int index ) const { - return coords[index]; - } - - 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(); } - - inline int Area() const { return area; } - inline const Coord &Centre() const { - return centre; - } - bool isInside( const Coord &coord ) const; + int32 area; + Vector2 centre; }; #endif // ZM_POLY_H diff --git a/src/zm_regexp.h b/src/zm_regexp.h index 19a13f234..9c9ec5675 100644 --- a/src/zm_regexp.h +++ b/src/zm_regexp.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 c1a252c3d..3f45578ec 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -49,45 +49,43 @@ RemoteCamera::RemoteCamera( mNeedAuth(false), mAuthenticator(nullptr) { - if ( path[0] != '/' ) + if (path[0] != '/') path = '/'+path; } RemoteCamera::~RemoteCamera() { - if ( hp != nullptr ) { + if (hp != nullptr) { freeaddrinfo(hp); hp = nullptr; } - if ( mAuthenticator ) { + if (mAuthenticator) { delete mAuthenticator; mAuthenticator = nullptr; } } void RemoteCamera::Initialise() { - if( protocol.empty() ) - Fatal( "No protocol specified for remote camera" ); + if (protocol.empty()) + Fatal("No protocol specified for remote camera"); - if( host.empty() ) - Fatal( "No host specified for remote camera" ); + if (host.empty()) + Fatal("No host specified for remote camera"); - if ( port.empty() ) + if (port.empty()) Fatal("No port specified for remote camera"); - //if( path.empty() ) - //Fatal( "No path specified for remote camera" ); // Cache as much as we can to speed things up - std::string::size_type authIndex = host.rfind( '@' ); + std::string::size_type authIndex = host.rfind('@'); - if ( authIndex != std::string::npos ) { - auth = host.substr( 0, authIndex ); - host.erase( 0, authIndex+1 ); - auth64 = base64Encode( auth ); + if (authIndex != std::string::npos) { + auth = host.substr(0, authIndex); + host.erase(0, authIndex+1); + auth64 = Base64Encode(auth); - authIndex = auth.rfind( ':' ); + authIndex = auth.rfind(':'); username = auth.substr(0,authIndex); - password = auth.substr( authIndex+1, auth.length() ); + password = auth.substr(authIndex+1, auth.length()); } mNeedAuth = false; @@ -99,12 +97,13 @@ void RemoteCamera::Initialise() { hints.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); - if ( ret != 0 ) { - Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); + if (ret != 0) { + Error("Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret)); + return; } struct addrinfo *p = nullptr; int addr_count = 0; - for ( p = hp; p != nullptr; p = p->ai_next ) { + for (p = hp; p != nullptr; p = p->ai_next) { addr_count++; } Debug(1, "%d addresses returned", addr_count); diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index 3c7f9f698..e08975af2 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -81,10 +81,10 @@ public: 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(ZMPacket &p) = 0; - virtual int PostCapture() = 0; + virtual int PreCapture() override { return 0; }; + virtual int PrimeCapture() override { return 0; }; + virtual int Capture(std::shared_ptr &p) override = 0; + virtual int PostCapture() override = 0; int Read(int fd, char*buf, int size); }; diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 1a4da254e..6169149da 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -104,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"; @@ -143,6 +144,14 @@ void RemoteCameraHttp::Initialise() { int RemoteCameraHttp::Connect() { struct addrinfo *p = nullptr; + if (!hp) { + RemoteCamera::Initialise(); + if (!hp) { + Error("Unable to resolve address for remote camera, aborting"); + return -1; + } + } + for ( p = hp; p != nullptr; p = p->ai_next ) { sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); if ( sd < 0 ) { @@ -201,7 +210,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); @@ -209,44 +218,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. @@ -260,53 +274,54 @@ 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(4, "Just getting 32K" ); + Debug(4, "Just getting 32K"); } else { - Debug(4, "Just getting %d", total_bytes_to_read ); + Debug(4, "Just getting %d", total_bytes_to_read); } } // end if bytes_expected or not - Debug( 4, "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 )) - return -1; - Debug(4, "Timeout waiting for REGEXP HEADER"); - usleep(100000); - } - return buffer_len; + TimePoint start_time = std::chrono::steady_clock::now(); + int buffer_len; + while (!(buffer_len = ReadData(buffer))) { + if (zm_terminate or std::chrono::steady_clock::now() - start_time > FPSeconds(config.watch_max_delay)) { + return -1; + } + + Debug(4, "Timeout waiting for REGEXP HEADER"); + std::this_thread::sleep_for(Milliseconds(100)); + } + return buffer_len; } int RemoteCameraHttp::GetResponse() { - int buffer_len; #if HAVE_LIBPCRE if ( method == REGEXP ) { const char *header = nullptr; @@ -327,7 +342,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; @@ -361,7 +376,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"; @@ -423,12 +438,12 @@ int RemoteCameraHttp::GetResponse() { //// MPEG stream, coming soon! //} else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Error("Unrecognised content type '%s'", content_type); + return -1; } - buffer.consume( header_len ); + buffer.consume(header_len); } else { - Debug( 3, "Unable to extract header from stream, retrying" ); + Debug(3, "Unable to extract header from stream, retrying"); //return( -1 ); } break; @@ -467,7 +482,7 @@ int RemoteCameraHttp::GetResponse() { state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); - buffer_len = GetData(); + int buffer_len = GetData(); if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); @@ -496,36 +511,36 @@ int RemoteCameraHttp::GetResponse() { 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 (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 ); + 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 { - while ( !content_length ) { - buffer_len = GetData(); - if ( buffer_len < 0 ) { - Error( "Unable to read content" ); - return( -1 ); + 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 ) { + 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 ); + 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 ) { + 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 ); + Debug(3, "Got end of image by pattern, content-length = %d", content_length); } } } @@ -575,10 +590,7 @@ int RemoteCameraHttp::GetResponse() { 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; @@ -598,10 +610,9 @@ int RemoteCameraHttp::GetResponse() { static char content_boundary[64]; static int content_boundary_len; - while ( !zm_terminate ) { - switch ( state ) { + while (!zm_terminate) { + switch (state) { case HEADER : - { n_headers = 0; http_header = nullptr; connection_header = nullptr; @@ -617,29 +628,28 @@ int RemoteCameraHttp::GetResponse() { content_type[0] = '\0'; content_boundary[0] = '\0'; content_boundary_len = 0; - [[gnu::fallthrough]]; - } + FALLTHROUGH; case HEADERCONT : { - buffer_len = GetData(); - if ( buffer_len < 0 ) { + int 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; + char *header_ptr = buffer; int header_len = buffer.size(); bool all_headers = false; - while ( true ) { + while (!zm_terminate) { int crlf_len = memspn(header_ptr, "\r\n", header_len); - if ( n_headers ) { + if (n_headers) { if ( - (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len )) + (crlf_len == 2 && !strncmp(header_ptr, "\n\n", crlf_len)) || - (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\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'; @@ -649,8 +659,8 @@ int RemoteCameraHttp::GetResponse() { break; } } - if ( crlf_len ) { - if ( header_len == crlf_len ) { + if (crlf_len) { + if (header_len == crlf_len) { break; } else { *header_ptr = '\0'; @@ -660,22 +670,22 @@ int RemoteCameraHttp::GetResponse() { } Debug(6, "%s", header_ptr); - if ( (crlf = mempbrk(header_ptr, "\r\n", header_len)) ) { + 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) ) { + 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 ); + 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 ); + 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 ); + 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 ); + 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); @@ -690,10 +700,10 @@ int RemoteCameraHttp::GetResponse() { } } // end while search for headers - if ( all_headers ) { + if (all_headers) { char *start_ptr, *end_ptr; - if ( !http_header ) { + if (!http_header) { Error("Unable to extract HTTP status from header"); return -1; } @@ -702,7 +712,7 @@ int RemoteCameraHttp::GetResponse() { 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)); + //memset(http_version, 0, sizeof(http_version)); strncpy(http_version, start_ptr, end_ptr-start_ptr); start_ptr = end_ptr; @@ -717,12 +727,12 @@ int RemoteCameraHttp::GetResponse() { start_ptr += strspn(start_ptr, " "); strcpy(status_mesg, start_ptr); - if ( status == 401 ) { - if ( mNeedAuth ) { + if (status == 401) { + if (mNeedAuth) { Error("Failed authentication"); return -1; } - if ( !authenticate_header ) { + if (!authenticate_header) { Error("Failed authentication, but don't have an authentication header."); return -1; } @@ -731,13 +741,13 @@ int RemoteCameraHttp::GetResponse() { Debug(2, "Checking for digest auth in %s", authenticate_header); mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + 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 += "Connection: Keep-Alive\r\n"; request += mAuthenticator->getAuthHeader("GET", path.c_str()); request += "\r\n"; @@ -746,26 +756,26 @@ int RemoteCameraHttp::GetResponse() { } else { Debug(2, "Need some other kind of Authentication"); } - } else if ( status < 200 || status > 299 ) { + } 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 ) { + 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 ) { + if (content_length_header) { start_ptr = content_length_header + strspn(content_length_header, " "); - content_length = atoi( start_ptr ); + content_length = atoi(start_ptr); Debug(3, "Got content length '%d'", content_length); } - if ( content_type_header ) { - memset(content_type, 0, sizeof(content_type)); + 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); @@ -773,7 +783,7 @@ int RemoteCameraHttp::GetResponse() { start_ptr = end_ptr + strspn(end_ptr, "; "); - if ( strncasecmp(start_ptr, boundary_match, boundary_match_len) == 0 ) { + 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); @@ -787,24 +797,24 @@ int RemoteCameraHttp::GetResponse() { } } // end if content_type_header - if ( !strcasecmp(content_type, "image/jpeg") || !strcasecmp(content_type, "image/jpg") ) { + 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") ) { + } 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") ) { + } 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") ) { + } else if (!strcasecmp(content_type, "multipart/x-mixed-replace")) { // Image stream, so start processing - if ( !content_boundary[0] ) { + if (!content_boundary[0]) { Error("No content boundary found in header '%s'", content_type_header); return -1; } @@ -816,8 +826,8 @@ int RemoteCameraHttp::GetResponse() { //// MPEG stream, coming soon! //} else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Error("Unrecognised content type '%s'", content_type); + return -1; } } else { Debug(3, "Unable to extract entire header from stream, continuing"); @@ -827,15 +837,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'; - [[gnu::fallthrough]]; - } + FALLTHROUGH; case SUBHEADERCONT : { char *crlf = nullptr; @@ -843,24 +851,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); } } @@ -904,29 +915,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; @@ -934,97 +945,96 @@ 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; } } @@ -1047,7 +1057,7 @@ int RemoteCameraHttp::PrimeCapture() { mode = SINGLE_IMAGE; buffer.clear(); } - get_VideoStream(); + getVideoStream(); return 1; } @@ -1069,47 +1079,48 @@ int RemoteCameraHttp::PreCapture() { return 1; } // end int RemoteCameraHttp::PreCapture() -int RemoteCameraHttp::Capture(ZMPacket &packet) { +int RemoteCameraHttp::Capture(std::shared_ptr &packet) { int content_length = GetResponse(); - if ( content_length == 0 ) { + if (content_length == 0) { Warning("Unable to capture image, retrying"); return 0; } - if ( content_length < 0 ) { + if (content_length < 0) { Error("Unable to get response, disconnecting"); return -1; } - if ( !packet.image ) { - Debug(1, "Allocating image"); - packet.image = new Image(width, height, colours, subpixelorder); + 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; + Image *image = packet->image; + packet->keyframe = 1; + packet->codec_type = AVMEDIA_TYPE_VIDEO; + packet->packet.stream_index = mVideoStreamId; + packet->stream = mVideoStream; - switch ( format ) { + switch (format) { case JPEG : - if ( !image->DecodeJpeg(buffer.extract(content_length), content_length, colours, subpixelorder) ) { + 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() ) { + 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, imagesize); + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); break; case X_RGBZ : - if ( !image->Unzip( buffer.extract( content_length ), content_length ) ) { + if (!image->Unzip(buffer.extract(content_length), content_length)) { Error("Unable to unzip RGB image"); return -1; } - image->Assign(width, height, colours, subpixelorder, buffer, imagesize); + image->Assign(width, height, colours, subpixelorder, buffer.head(), imagesize); break; default : Error("Unexpected image format encountered"); diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index baf1dc0f4..794ccba78 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -59,19 +59,19 @@ public: ); ~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( ZMPacket &p ); - int PostCapture(); - int Close() { Disconnect(); return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &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 4adc2d957..92f806eed 100644 --- a/src/zm_remote_camera_nvsocket.cpp +++ b/src/zm_remote_camera_nvsocket.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef SOLARIS #include // FIONREAD and friends @@ -183,9 +184,9 @@ int RemoteCameraNVSocket::PrimeCapture() { return 0; } -int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { - if ( SendRequest("GetNextImage\n") < 0 ) { - Warning( "Unable to capture image, retrying" ); +int RemoteCameraNVSocket::Capture(std::shared_ptr &zm_packet) { + if (SendRequest("GetNextImage\n") < 0) { + Warning("Unable to capture image, retrying"); return 0; } int bytes_read = Read(sd, buffer, imagesize); @@ -194,17 +195,17 @@ int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { return 0; } uint32_t end; - if ( Read(sd, (char *) &end , sizeof(end)) < 0 ) { + if (Read(sd, (char *) &end , sizeof(end)) < 0) { Warning("Unable to capture image, retrying"); return 0; } - if ( end != 0xFFFFFFFF) { + if (end != 0xFFFFFFFF) { Warning("End Bytes Failed\n"); return 0; } - zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); - zm_packet.keyframe = 1; + 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 5d021a8ff..d3b5ce8e9 100644 --- a/src/zm_remote_camera_nvsocket.h +++ b/src/zm_remote_camera_nvsocket.h @@ -47,16 +47,16 @@ public: bool p_record_audio ); ~RemoteCameraNVSocket(); - void Initialise(); - void Terminate() { Disconnect(); } - int Connect(); - int Disconnect(); + void Initialise() override; + void Terminate() override { Disconnect(); } + int Connect() override; + int Disconnect() override; int SendRequest(std::string); int GetResponse(); - int PrimeCapture(); - int Capture(ZMPacket &p); - int PostCapture(); - int Close() { return 0; }; + int PrimeCapture() override; + int Capture(std::shared_ptr &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 dbd489e18..8cdecdf94 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -23,8 +23,6 @@ #include "zm_monitor.h" #include "zm_packet.h" -#if HAVE_LIBAVFORMAT - RemoteCameraRtsp::RemoteCameraRtsp( const Monitor *monitor, const std::string &p_method, @@ -48,7 +46,6 @@ RemoteCameraRtsp::RemoteCameraRtsp( p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio), rtsp_describe(p_rtsp_describe), - rtspThread(0), frameCount(0) { if ( p_method == "rtpUni" ) @@ -114,29 +111,26 @@ 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; } int RemoteCameraRtsp::PrimeCapture() { Debug(2, "Waiting for sources"); - for ( int i = 0; (i < 100) && !rtspThread->hasSources(); i++ ) { - usleep(100000); + for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) { + std::this_thread::sleep_for(Microseconds(100)); } - if ( !rtspThread->hasSources() ) { + + if (!rtspThread->hasSources()) { Error("No RTSP sources"); return -1; } @@ -180,12 +174,8 @@ int RemoteCameraRtsp::PrimeCapture() { Debug(3, "Unable to locate audio stream"); // Get a pointer to the codec context for the video stream -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - mVideoCodecContext = avcodec_alloc_context3(NULL); + mVideoCodecContext = avcodec_alloc_context3(nullptr); avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); -#else - mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; -#endif // Find the decoder for the video stream AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); @@ -193,28 +183,20 @@ int RemoteCameraRtsp::PrimeCapture() { Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); // Open codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( avcodec_open(mVideoCodecContext, codec) < 0 ) -#else - if ( avcodec_open2(mVideoCodecContext, codec, 0) < 0 ) -#endif + if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) Panic("Can't open codec"); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); -#else - int pSize = avpicture_get_size(imagePixFormat, width, height); -#endif if ( (unsigned int)pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %d", pSize, imagesize); + Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); } 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"); @@ -223,18 +205,18 @@ int RemoteCameraRtsp::PreCapture() { return 1; } -int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { +int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { int frameComplete = false; - AVPacket *packet = &zm_packet.packet; - if ( !zm_packet.image ) { + 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); + zm_packet->image = new Image(width, height, colours, subpixelorder); } - while ( !frameComplete ) { + while (!frameComplete) { buffer.clear(); - if ( !rtspThread->isRunning() ) + if (!rtspThread || rtspThread->IsStopped()) return -1; if ( rtspThread->getFrame(buffer) ) { @@ -259,7 +241,7 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { continue; } else if ( nalType == 5 ) { packet->flags |= AV_PKT_FLAG_KEY; - zm_packet.keyframe = 1; + zm_packet->keyframe = 1; // IDR buffer += lastSps; buffer += lastPps; @@ -280,35 +262,23 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { gettimeofday(&now, NULL); packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; - int bytes_consumed = zm_packet.decode(mVideoCodecContext); + 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 -= 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 - codec -#endif - ->width ) { + zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode"); + if (!mVideoStream->codecpar->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) + mVideoStream->codecpar->width = zm_packet->in_frame->width; + mVideoStream->codecpar->height = zm_packet->in_frame->height; zm_dump_codecpar(mVideoStream->codecpar); -#endif } - zm_packet.codec_type = mVideoCodecContext->codec_type; + 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; @@ -324,4 +294,3 @@ int RemoteCameraRtsp::Capture(ZMPacket &zm_packet) { int RemoteCameraRtsp::PostCapture() { return 1; } -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index bd8b30a7c..4880f6f35 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -44,14 +44,12 @@ protected: RtspThread::RtspMethod method; - RtspThread *rtspThread; + std::unique_ptr rtspThread; int frameCount; -#if HAVE_LIBAVFORMAT AVFormatContext *mFormatContext; _AVPIXELFORMAT imagePixFormat; -#endif // HAVE_LIBAVFORMAT public: RemoteCameraRtsp( @@ -72,16 +70,16 @@ public: 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(ZMPacket &p); - int PostCapture(); - int Close() { return 0; }; + int PrimeCapture() override; + int PreCapture() override; + int Capture(std::shared_ptr &p) override; + int PostCapture() override; + int Close() override { return 0; }; AVStream *get_VideoStream() { if ( mVideoStreamId != -1 ) diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index a978d2f2d..25d34f0ff 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -23,11 +23,16 @@ #include "zm_rtp.h" #include "zm_rtsp.h" -#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 ) { @@ -121,7 +126,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 @@ -241,12 +246,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() ); - ZM::SockAddrInet localAddr, remoteAddr; + zm::SockAddrInet localAddr, remoteAddr; bool sendReports; - ZM::UdpInetSocket rtpCtrlServer; + zm::UdpInetSocket rtpCtrlServer; if ( mRtpSource.getLocalHost() != "" ) { if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) ) Fatal( "Failed to bind RTCP server" ); @@ -264,18 +269,17 @@ 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 - ZM::Select select( 10 ); + zm::Select select(Seconds(10)); select.addReader( &rtpCtrlServer ); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - 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. + TimePoint last_receive = std::chrono::steady_clock::now(); + 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 ) { - - time_t now = time(nullptr); - ZM::Select::CommsList readable = select.getReadable(); + while (!mTerminate && select.wait() >= 0) { + TimePoint now = std::chrono::steady_clock::now(); + 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 @@ -283,24 +287,23 @@ 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: %.2f s", + bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), FPSeconds(now - last_receive).count()); 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: %.2f s", FPSeconds(now - last_receive).count()); continue; //break; } } else { timeout = false; - last_receive = time(nullptr); + last_receive = std::chrono::steady_clock::now(); } - for ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( ZM::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() ); @@ -318,7 +321,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 { @@ -327,8 +330,5 @@ 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 5fd428ead..9346496f7 100644 --- a/src/zm_rtp_ctrl.h +++ b/src/zm_rtp_ctrl.h @@ -20,7 +20,9 @@ #ifndef ZM_RTP_CTRL_H #define ZM_RTP_CTRL_H -#include "zm_thread.h" +#include +#include +#include // Defined in ffmpeg rtp.h //#define RTP_MAX_SDES 255 // maximum text length for SDES @@ -32,7 +34,7 @@ class RtspThread; class RtpSource; -class RtpCtrlThread : public Thread { +class RtpCtrlThread { friend class RtspThread; private: @@ -121,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 ); @@ -129,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 1d8443a37..e8ce628f2 100644 --- a/src/zm_rtp_data.cpp +++ b/src/zm_rtp_data.cpp @@ -23,11 +23,16 @@ #include "zm_rtsp.h" #include "zm_signal.h" -#if HAVE_LIBAVFORMAT - 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) { @@ -54,12 +59,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()); - ZM::SockAddrInet localAddr; - ZM::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"); @@ -71,25 +76,25 @@ int RtpDataThread::run() { } Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()); - ZM::Select select(3); + zm::Select select(Seconds(3)); select.addReader(&rtpDataSocket); unsigned char buffer[ZM_NETWORK_BUFSIZ]; - while ( !zm_terminate && !mStop && (select.wait() >= 0) ) { - ZM::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 ( ZM::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) { - if ( ZM::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 { @@ -98,8 +103,5 @@ 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 9ecef8305..e20878212 100644 --- a/src/zm_rtp_data.h +++ b/src/zm_rtp_data.h @@ -21,7 +21,8 @@ #define ZM_RTP_DATA_H #include "zm_define.h" -#include "zm_thread.h" +#include +#include class RtspThread; class RtpSource; @@ -40,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 e9edfb821..1862c1886 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -23,8 +23,7 @@ #include "zm_rtp_data.h" #include "zm_utils.h" #include - -#if HAVE_LIBAVCODEC +#include RtpSource::RtpSource( int id, @@ -67,18 +66,24 @@ RtpSource::RtpSource( mRtpFactor = mRtpClock; - mBaseTimeReal = tvNow(); - mBaseTimeNtp = tvZero(); + mBaseTimeReal = std::chrono::system_clock::now(); + mBaseTimeNtp = {}; mBaseTimeRtp = rtpTime; - mLastSrTimeReal = tvZero(); - mLastSrTimeNtp = tvZero(); + mLastSrTimeReal = {}; + mLastSrTimeNtp = {}; mLastSrTimeRtp = 0; if ( mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4 ) 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; @@ -154,18 +159,19 @@ bool RtpSource::updateSeq(uint16_t seq) { } void RtpSource::updateJitter( const RtpDataHeader *header ) { - if ( mRtpFactor > 0 ) { - uint32_t localTimeRtp = mBaseTimeRtp + uint32_t(tvDiffSec(mBaseTimeReal) * mRtpFactor); + if (mRtpFactor > 0) { + SystemTimePoint now = std::chrono::system_clock::now(); + FPSeconds time_diff = std::chrono::duration_cast(now - mBaseTimeReal); + + uint32_t localTimeRtp = mBaseTimeRtp + static_cast(time_diff.count() * 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", + time_diff.count(), + localTimeRtp, + ntohl(header->timestampN), + packetTransit); if ( mTransit > 0 ) { // Jitter @@ -187,12 +193,13 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime) { - struct timeval ntpTime = tvMake(ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16))); + timeval ntpTime = zm::chrono::duration_cast( + Seconds(ntpTimeSecs) + Microseconds((Microseconds::period::den * (ntpTimeFrac >> 16)) / (1 << 16))); Debug(5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); if ( mBaseTimeNtp.tv_sec == 0 ) { - mBaseTimeReal = tvNow(); + mBaseTimeReal = std::chrono::system_clock::now(); mBaseTimeNtp = ntpTime; mBaseTimeRtp = rtpTime; } else if ( !mRtpClock ) { @@ -201,12 +208,14 @@ void RtpSource::updateRtcpData( mLastSrTimeNtp.tv_sec, mLastSrTimeNtp.tv_usec, rtpTime, ntpTime.tv_sec, ntpTime.tv_usec, rtpTime); - double diffNtpTime = tvDiffSec( mBaseTimeNtp, ntpTime ); + FPSeconds diffNtpTime = + zm::chrono::duration_cast(ntpTime) - zm::chrono::duration_cast(mBaseTimeNtp); + uint32_t diffRtpTime = rtpTime - mBaseTimeRtp; - mRtpFactor = (uint32_t)(diffRtpTime / diffNtpTime); + mRtpFactor = static_cast(diffRtpTime / diffNtpTime.count()); Debug( 5, "NTP-diff: %.6f RTP-diff: %d RTPfactor: %d", - diffNtpTime, diffRtpTime, mRtpFactor); + diffNtpTime.count(), diffRtpTime, mRtpFactor); } mLastSrTimeNtpSecs = ntpTimeSecs; mLastSrTimeNtpFrac = ntpTimeFrac; @@ -232,19 +241,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 +295,7 @@ bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) { extraHeader = 2; break; - default: + default: Debug(3, "Unhandled nalType %d", nalType); } @@ -301,7 +304,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 +315,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,19 +357,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; } - -#endif // HAVE_LIBAVCODEC diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index 522ba4324..a39e8225f 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -24,12 +24,12 @@ #include "zm_config.h" #include "zm_define.h" #include "zm_ffmpeg.h" -#include "zm_thread.h" +#include "zm_time.h" +#include +#include #include #include -#if HAVE_LIBAVCODEC - struct RtpDataHeader; class RtpSource @@ -69,7 +69,7 @@ private: // Time keys uint32_t mRtpClock; uint32_t mRtpFactor; - struct timeval mBaseTimeReal; + SystemTimePoint mBaseTimeReal; struct timeval mBaseTimeNtp; uint32_t mBaseTimeRtp; @@ -90,14 +90,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 ); @@ -183,6 +192,4 @@ public: } }; -#endif // HAVE_LIBAVCODEC - #endif // ZM_RTP_SOURCE_H diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 67373813e..1daecf18b 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -26,15 +26,13 @@ #include -#if HAVE_LIBAVFORMAT - int RtspThread::smMinDataPort = 0; int RtspThread::smMaxDataPort = 0; 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]); } @@ -42,7 +40,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)); @@ -88,19 +86,11 @@ bool RtspThread::recvResponse(std::string &response) { int RtspThread::requestPorts() { if ( !smMinDataPort ) { - 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)); - } + std::string sql = "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC"; + + MYSQL_RES *result = zmDbFetch(sql); - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } int nMonitors = mysql_num_rows(result); int position = 0; if ( nMonitors ) { @@ -161,7 +151,7 @@ RtspThread::RtspThread( mSsrc(0), mDist(UNDEFINED), mRtpTime(0), - mStop(false) + mTerminate(false) { mUrl = mProtocol+"://"+mHost+":"+mPort; if ( !mPath.empty() ) { @@ -179,21 +169,23 @@ 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); -#else - av_free_format_context(mFormatContext); -#endif mFormatContext = nullptr; } if ( mSessDesc ) { @@ -204,7 +196,7 @@ RtspThread::~RtspThread() { mAuthenticator = nullptr; } -int RtspThread::run() { +void RtspThread::Run() { std::string message; std::string response; @@ -246,11 +238,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()); @@ -264,7 +256,7 @@ int RtspThread::run() { if ( response.size() ) 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 @@ -282,7 +274,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"; @@ -295,7 +287,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 ) @@ -305,23 +297,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); @@ -338,7 +330,8 @@ int RtspThread::run() { authTried = true; sendCommand(message); // FIXME Why sleep 1? - usleep(10000); + std::this_thread::sleep_for(Microseconds(10)); + res = recvResponse(response); if ( !res && respCode==401 ) mNeedAuth = true; @@ -347,17 +340,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; } @@ -373,8 +366,8 @@ int RtspThread::run() { mSessDesc = new SessionDescriptor( mUrl, sdp ); mFormatContext = mSessDesc->generateFormatContext(); } catch ( const Exception &e ) { - Error(e.getMessage().c_str()); - return -1; + Error("%s", e.getMessage().c_str()); + return; } #if 0 @@ -399,17 +392,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 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 ) -#endif - { + if (mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // 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() != '/' ) { @@ -419,11 +405,7 @@ int RtspThread::run() { } } 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; -#endif break; } // end if is video } // end foreach stream @@ -450,21 +432,24 @@ int RtspThread::run() { } if ( !sendCommand(message) ) - return -1; + return; if ( !recvResponse(response) ) - return -1; + return; - lines = split(response, "\r\n"); + lines = Split(response, "\r\n"); std::string session; - int timeout = 0; + Seconds timeout = Seconds(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 ( sessionLine.size() == 2 ) - sscanf(trimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout); + StringVector sessionLine = Split(lines[i].substr(9), ";"); + session = TrimSpaces(sessionLine[0]); + if ( sessionLine.size() == 2 ){ + int32 timeout_val = 0; + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val); + timeout = Seconds(timeout_val); + } } sscanf(lines[i].c_str(), "Transport: %s", transport); } @@ -472,7 +457,9 @@ int RtspThread::run() { if ( session.empty() ) 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 %" PRIi64 " secs", + session.c_str(), + static_cast(Seconds(timeout).count())); if ( !transport[0] ) Fatal("Unable to get transport details from response '%s'", response.c_str()); @@ -484,33 +471,33 @@ int RtspThread::run() { 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 ); } } @@ -525,22 +512,28 @@ int RtspThread::run() { message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n"; if ( !sendCommand(message) ) - return -1; + return; if ( !recvResponse(response) ) - return -1; + 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)); + 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); - if ( timeout > 0 ) - Debug(2, "Got timeout %d secs from PLAY command response", timeout); + if ((lines[i].size() > 8) && (lines[i].substr(0, 8) == "Session:") && (timeout == Seconds(0))) { + StringVector sessionLine = Split(lines[i].substr(9), ";"); + if ( sessionLine.size() == 2 ){ + int32 timeout_val = 0; + sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val); + timeout = Seconds(timeout_val); + } + + if ( timeout > Seconds(0) ) { + Debug(2, "Got timeout %" PRIi64 " secs from PLAY command response", + static_cast(Seconds(timeout).count())); + } } } @@ -552,18 +545,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 ); } } @@ -575,8 +568,8 @@ int RtspThread::run() { Debug( 2, "RTSP Seq is %d", seq ); Debug( 2, "RTSP Rtptime is %ld", rtpTime ); - time_t lastKeepalive = time(nullptr); - time_t now; + TimePoint lastKeepalive = std::chrono::steady_clock::now(); + TimePoint now; message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; switch( mMethod ) { @@ -587,20 +580,22 @@ int RtspThread::run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - rtpDataThread.start(); - rtpCtrlThread.start(); - - while( !mStop ) { - now = time(nullptr); + while (!mTerminate) { + now = std::chrono::steady_clock::now(); // 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) ); - if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) { - if ( !sendCommand( message ) ) - return( -1 ); + Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 "s ", + sendKeepalive, + static_cast(Seconds(timeout).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(lastKeepalive.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast((now - lastKeepalive)).count())); + + if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) + return; lastKeepalive = now; } - usleep( 100000 ); + std::this_thread::sleep_for(Microseconds(100)); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -612,19 +607,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(); @@ -641,14 +633,14 @@ int RtspThread::run() { RtpDataThread rtpDataThread( *this, *source ); RtpCtrlThread rtpCtrlThread( *this, *source ); - ZM::Select select( double(config.http_timeout)/1000.0 ); + zm::Select select(Milliseconds(config.http_timeout)); 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 ) { - ZM::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; @@ -714,16 +706,23 @@ int RtspThread::run() { } // Send a keepalive message if the server supports this feature and we are close to the timeout expiration // FIXME: Is this really necessary when using tcp ? - now = time(nullptr); + now = std::chrono::steady_clock::now(); // 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) ); - if ( sendKeepalive && (timeout > 0) && ((now-lastKeepalive) > (timeout-5)) ) - { - if ( !sendCommand( message ) ) - return( -1 ); + Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 " s", + sendKeepalive, + static_cast(Seconds(timeout).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(lastKeepalive.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast((now - lastKeepalive)).count())); + + if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) { + return; + } + lastKeepalive = now; } - buffer.tidy( 1 ); + buffer.tidy(true); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -735,7 +734,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(); @@ -749,17 +748,17 @@ 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; - lastKeepalive = time(nullptr); + if (sendKeepalive && (timeout > Seconds(0)) + && ((std::chrono::steady_clock::now() - lastKeepalive) > (timeout - Seconds(5)))) { + if (!sendCommand(message)) { + return; + } + lastKeepalive = std::chrono::steady_clock::now(); } - usleep(100000); + std::this_thread::sleep_for(Microseconds(100)); } #if 0 message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n"; @@ -770,15 +769,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(); @@ -791,7 +787,5 @@ 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 26189cd20..96906e209 100644 --- a/src/zm_rtsp.h +++ b/src/zm_rtsp.h @@ -24,10 +24,12 @@ #include "zm_rtp_source.h" #include "zm_rtsp_auth.h" #include "zm_sdp.h" +#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; @@ -66,8 +68,8 @@ private: std::string mHttpSession; ///< Only for RTSP over HTTP sessions - ZM::TcpInetClient mRtspSocket; - ZM::TcpInetClient mRtspSocket2; + zm::TcpInetClient mRtspSocket; + zm::TcpInetClient mRtspSocket2; SourceMap mSources; @@ -84,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 ); @@ -124,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 138036fd1..cf2be15f4 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -18,29 +18,20 @@ #include "zm_rtsp_auth.h" +#include "zm_crypt.h" #include "zm_logger.h" #include "zm_utils.h" #include +#include namespace zm { -Authenticator::Authenticator( const std::string &username, const std::string &password) : - fCnonce("0a4f113b"), - fUsername(username), - fPassword(password) - { -#ifdef HAVE_GCRYPT_H - // Special initialisation for libgcrypt - if ( !gcry_check_version(GCRYPT_VERSION) ) { - Fatal("Unable to initialise libgcrypt"); - } - gcry_control( GCRYCTL_DISABLE_SECMEM, 0 ); - gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 ); -#endif // HAVE_GCRYPT_H - - fAuthMethod = AUTH_UNDEFINED; - nc = 1; -} +Authenticator::Authenticator(std::string username, std::string password) + : fAuthMethod(AUTH_UNDEFINED), + fCnonce("0a4f113b"), + fUsername(std::move(username)), + fPassword(std::move(password)), + nc(1) {} Authenticator::~Authenticator() { reset(); @@ -68,21 +59,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; } } @@ -92,13 +83,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 Authenticator::getAuthHeader(const std::string &method, const 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()) + "\", " + @@ -127,74 +118,42 @@ std::string Authenticator::getAuthHeader(std::string method, std::string uri) { return result; } -std::string Authenticator::computeDigestResponse(std::string &method, std::string &uri) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT +std::string Authenticator::computeDigestResponse(const std::string &method, const std::string &uri) { // The "response" field is computed as: // md5(md5(::)::md5(:)) - size_t md5len = 16; - unsigned char md5buf[md5len]; - char md5HexBuf[md5len*2+1]; - + // Step 1: md5(::) std::string ha1Data = username() + ":" + realm() + ":" + password(); - Debug( 2, "HA1 pre-md5: %s", ha1Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), (unsigned int)ha1Data.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha1Hash = md5HexBuf; - + Debug(2, "HA1 pre-md5: %s", ha1Data.c_str()); + + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(ha1Data); + std::string ha1Hash = ByteArrayToHexString(md5_digest); + // Step 2: md5(:) std::string ha2Data = method + ":" + uri; - Debug( 2, "HA2 pre-md5: %s", ha2Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), (unsigned int)ha2Data.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha2Hash = md5HexBuf; + Debug(2, "HA2 pre-md5: %s", ha2Data.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(ha2Data); + std::string ha2Hash = ByteArrayToHexString(md5_digest); // Step 3: md5(ha1::ha2) std::string digestData = ha1Hash + ":" + nonce(); - if ( ! fQop.empty() ) { - digestData += ":" + stringtf("%08x", nc) + ":"+fCnonce + ":" + fQop; - nc ++; + if (!fQop.empty()) { + digestData += ":" + stringtf("%08x", nc) + ":" + fCnonce + ":" + fQop; + nc++; // if qop was specified, then we have to include t and a cnonce and an nccount } digestData += ":" + ha2Hash; - Debug( 2, "pre-md5: %s", digestData.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), (unsigned int)digestData.length() }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len ); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - - return md5HexBuf; -#else // HAVE_DECL_MD5 - Error("You need to build with gnutls or openssl installed to use digest authentication"); - return 0; -#endif // HAVE_DECL_MD5 + Debug(2, "pre-md5: %s", digestData.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(digestData); + + return ByteArrayToHexString(md5_digest); } -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); @@ -205,13 +164,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 8721ab2e6..52d186d26 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -22,22 +22,12 @@ #include "zm_config.h" #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_GCRYPT_H -#include -#elif HAVE_LIBCRYPTO -#include -#endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO - -namespace zm { +namespace zm { enum AuthMethod { AUTH_UNDEFINED = 0, AUTH_BASIC = 1, AUTH_DIGEST = 2 }; class Authenticator { public: - Authenticator(const std::string &username, const std::string &password); + Authenticator(std::string username, std::string password); virtual ~Authenticator(); void reset(); @@ -46,10 +36,10 @@ public: std::string username() { return fUsername; } AuthMethod auth_method() const { return fAuthMethod; } - std::string computeDigestResponse( std::string &cmd, std::string &url ); - void authHandleHeader( std::string headerData ); - std::string getAuthHeader( std::string method, std::string path ); - void checkAuthResponse(std::string &response); + std::string computeDigestResponse(const std::string &cmd, const std::string &url); + void authHandleHeader(std::string headerData); + std::string getAuthHeader(const std::string &method, const std::string &path); + 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..ea6f4e2b7 --- /dev/null +++ b/src/zm_rtsp_server.cpp @@ -0,0 +1,346 @@ +// +// 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); + } + + std::unordered_map sessions; + std::unordered_map video_sources; + std::unordered_map audio_sources; + std::unordered_map> monitors; + + while (!zm_terminate) { + std::unordered_map> old_monitors = monitors; + + std::vector> new_monitors = Monitor::LoadMonitors(where, Monitor::QUERY); + for (const auto &monitor : new_monitors) { + auto old_monitor_it = old_monitors.find(monitor->Id()); + if (old_monitor_it != old_monitors.end() + and + (old_monitor_it->second->GetRTSPStreamName() == monitor->GetRTSPStreamName()) + ) { + Debug(1, "Found monitor in oldmonitors, clearing it"); + old_monitors.erase(old_monitor_it); + } 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()); + sessions.erase(mid); + } + } + + for (auto it = monitors.begin(); it != monitors.end(); ++it) { + auto &monitor = it->second; + + if (!monitor->ShmValid()) { + Debug(1, "!ShmValid"); + 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()); + sessions.erase(monitor->Id()); + } + monitor->Reload(); // This is to pickup change of colours, width, height, etc + continue; + } // end if failed to connect + } // end if !ShmValid + + 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 for %s", videoFifoPath.c_str()); + rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); + 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(10); + + 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.h b/src/zm_rtsp_server_adts_source.h index d8516bc30..bae93b8bd 100644 --- a/src/zm_rtsp_server_adts_source.h +++ b/src/zm_rtsp_server_adts_source.h @@ -28,8 +28,6 @@ class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { AVStream * stream, unsigned int queueSize ) { - Debug(1, "m_stream %p codecpar %p channels %d", - stream, stream->codecpar, stream->codecpar->channels); return new ADTS_ZoneMinderDeviceSource(env, std::move(monitor), stream, queueSize); }; protected: @@ -50,12 +48,7 @@ class ADTS_ZoneMinderDeviceSource : public ZoneMinderDeviceSource { int samplingFrequency() { return m_stream->codecpar->sample_rate; }; const char *configStr() { return config.c_str(); }; int numChannels() { - Debug(1, "this %p m_stream %p channels %d", - this, m_stream, channels); - Debug(1, "m_stream %p codecpar %p channels %d => %d", - m_stream, m_stream->codecpar, m_stream->codecpar->channels, channels); return channels; - return m_stream->codecpar->channels; } protected: 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 index abd1f8381..ba648b2f4 100644 --- a/src/zm_rtsp_server_device_source.cpp +++ b/src/zm_rtsp_server_device_source.cpp @@ -23,13 +23,13 @@ ZoneMinderDeviceSource::ZoneMinderDeviceSource( unsigned int queueSize ) : FramedSource(env), + m_eventTriggerId(envir().taskScheduler().createEventTrigger(ZoneMinderDeviceSource::deliverFrameStub)), 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 ) { 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..bc979d0a6 --- /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, + const 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..4a8f86e85 --- /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, + const 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_h264_device_source.cpp b/src/zm_rtsp_server_fifo_h264_source.cpp similarity index 61% rename from src/zm_rtsp_server_h264_device_source.cpp rename to src/zm_rtsp_server_fifo_h264_source.cpp index c10852196..b96f3da8b 100644 --- a/src/zm_rtsp_server_h264_device_source.cpp +++ b/src/zm_rtsp_server_fifo_h264_source.cpp @@ -1,70 +1,70 @@ /* --------------------------------------------------------------------------- ** -** H264_DeviceSource.cpp +** H264_FifoSource.cpp ** ** H264 Live555 source ** ** -------------------------------------------------------------------------*/ -#include "zm_rtsp_server_h264_device_source.h" +#include "zm_rtsp_server_fifo_h264_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) +H264_ZoneMinderFifoSource::H264_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const 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); + //this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size); + m_hType = 264; } // split packet into frames -std::list< std::pair > H264_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { +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(1, "SPS_Size: %d bufSize %d", size, bufSize); + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); m_sps.assign((char*)buffer, size); + updateAux = true; break; case 8: - Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); m_pps.assign((char*)buffer, size); + updateAux = true; break; case 5: - Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); + 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: - Debug(1, "Unknown frametype!? %d %d", m_frameType, m_frameType & 0x1F); break; } - if ( !m_sps.empty() && !m_pps.empty() ) { + 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]; @@ -73,58 +73,68 @@ std::list< std::pair > H264_ZoneMinderDeviceSource::spli 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; + 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(1, "auxLine: %s", m_auxLine.c_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_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) +H265_ZoneMinderFifoSource::H265_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const 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); + // this->splitFrames(m_stream->codecpar->extradata, m_stream->codecpar->extradata_size); + m_hType = 265; } // split packet in frames std::list< std::pair > -H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) { - std::list< std::pair > frameList; +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(1, "VPS_Size: %d bufSize %d", size, bufSize); + Debug(4, "VPS_Size: %d bufSize %d", size, bufSize); m_vps.assign((char*)buffer,size); + updateAux = true; break; case 33: - Debug(1, "SPS_Size: %d bufSize %d", size, bufSize); + Debug(4, "SPS_Size: %d bufSize %d", size, bufSize); m_sps.assign((char*)buffer,size); + updateAux = true; break; case 34: - Debug(1, "PPS_Size: %d bufSize %d", size, bufSize); + Debug(4, "PPS_Size: %d bufSize %d", size, bufSize); m_pps.assign((char*)buffer,size); + updateAux = true; break; case 19: case 20: - Debug(1, "IDR_Size: %d bufSize %d", size, bufSize); + 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())); @@ -132,11 +142,11 @@ H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSiz } break; default: - Debug(1, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); + //Debug(4, "Unknown frametype!? %d %d", m_frameType, ((m_frameType & 0x7E) >> 1)); break; } - if ( !m_vps.empty() && !m_sps.empty() && !m_pps.empty() ) { + 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()); @@ -145,24 +155,24 @@ H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSiz 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 auxLine to %s", m_auxLine.c_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 - if ( bufSize ) { - Debug(1, "%d bytes remaining", bufSize); - } + frameSize = bufSize; return frameList; -} // end H265_ZoneMinderDeviceSource::splitFrames(unsigned char* frame, unsigned frameSize) +} // end H265_ZoneMinderFifoSource::splitFrames(unsigned char* frame, unsigned frameSize) -unsigned char * H26X_ZoneMinderDeviceSource::findMarker( +unsigned char * H26X_ZoneMinderFifoSource::findMarker( unsigned char *frame, size_t size, size_t &length ) { //Debug(1, "findMarker %p %d", frame, size); @@ -184,18 +194,18 @@ unsigned char * H26X_ZoneMinderDeviceSource::findMarker( } // extract a frame -unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { +unsigned char* H26X_ZoneMinderFifoSource::extractFrame(unsigned char* frame, size_t& size, size_t& outsize) { unsigned char *outFrame = nullptr; - Debug(1, "ExtractFrame: %p %d", frame, size); + 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 ) + if (size >= 3) startFrame = this->findMarker(frame, size, markerLength); - if ( startFrame != nullptr ) { - Debug(1, "startFrame: %p marker Length %d", startFrame, markerLength); + if (startFrame != nullptr) { + size_t endMarkerLength = 0; + Debug(4, "startFrame: %p marker Length %zu", startFrame, markerLength); m_frameType = startFrame[markerLength]; int remainingSize = size-(startFrame-frame+markerLength); @@ -203,7 +213,7 @@ unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, if ( remainingSize > 3 ) { endFrame = this->findMarker(startFrame+markerLength, remainingSize, endMarkerLength); } - Debug(1, "endFrame: %p marker Length %d, remaining size %d", endFrame, endMarkerLength, remainingSize); + Debug(4, "endFrame: %p marker Length %zu, remaining size %d", endFrame, endMarkerLength, remainingSize); if ( m_keepMarker ) { size -= startFrame-frame; @@ -219,9 +229,9 @@ unsigned char* H26X_ZoneMinderDeviceSource::extractFrame(unsigned char* frame, outsize = size; } size -= outsize; - Debug(1, "Have frame type: %d size %d, keepmarker %d", m_frameType, outsize, m_keepMarker); + Debug(4, "Have frame type: %d size %zu, keepmarker %d", m_frameType, outsize, m_keepMarker); } else if ( size >= sizeof(H264shortmarker) ) { - Info("No marker found"); + Info("No marker found size %zu", size); } return outFrame; 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..cf605f40a --- /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, + const 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, + const 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..40dbc14fc --- /dev/null +++ b/src/zm_rtsp_server_fifo_source.cpp @@ -0,0 +1,283 @@ +/* --------------------------------------------------------------------------- +** 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, + const 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 because couldn't getNextFrame"); + 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(3, "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(3, "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, 4096); + //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 %u of buffer.", m_buffer.size()); + 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) { + 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); + } + } + 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..eea7c8230 --- /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, + const 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..4c5b382bd --- /dev/null +++ b/src/zm_rtsp_server_fifo_video_source.cpp @@ -0,0 +1,33 @@ +/* --------------------------------------------------------------------------- +** 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, + const std::string &fifo + ) : + ZoneMinderFifoSource(rtspServer, sessionId, channelId, fifo), + m_width(0), + m_height(0) +{ + 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..8bbef12f5 --- /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, + const 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 index 4afa31958..363ac7995 100644 --- a/src/zm_rtsp_server_frame.h +++ b/src/zm_rtsp_server_frame.h @@ -15,17 +15,16 @@ const char H264shortmarker[] = {0,0,1}; class NAL_Frame { public: - NAL_Frame(unsigned char * buffer, size_t size, timeval timestamp) : + NAL_Frame(unsigned char * buffer, size_t size, int64 pts) : m_buffer(nullptr), m_size(size), - m_timestamp(timestamp), + m_pts(pts), m_ref_count(1) { m_buffer = new unsigned char[m_size]; - memcpy(m_buffer, buffer, m_size); + if (buffer) { + memcpy(m_buffer, buffer, m_size); + } }; - NAL_Frame(unsigned char* buffer, size_t size) : m_buffer(buffer), m_size(size) { - gettimeofday(&m_timestamp, NULL); - }; NAL_Frame& operator=(const NAL_Frame&); ~NAL_Frame() { delete[] m_buffer; @@ -36,7 +35,9 @@ class NAL_Frame { // 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)); @@ -53,14 +54,26 @@ class NAL_Frame { 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: - timeval m_timestamp; + int64 m_pts; private: int m_ref_count; }; #endif // HAVE_RTSP_SERVER -#endif // ZM_RTSP_SERVER_FRAME_H \ No newline at end of file +#endif // ZM_RTSP_SERVER_FRAME_H diff --git a/src/zm_rtsp_server_h264_device_source.h b/src/zm_rtsp_server_h264_device_source.h deleted file mode 100644 index 5c01717c5..000000000 --- a/src/zm_rtsp_server_h264_device_source.h +++ /dev/null @@ -1,104 +0,0 @@ -/* --------------------------------------------------------------------------- -** 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 index 72702c02d..c847b471e 100644 --- a/src/zm_rtsp_server_server_media_subsession.cpp +++ b/src/zm_rtsp_server_server_media_subsession.cpp @@ -8,6 +8,7 @@ #include "zm_config.h" #include "zm_rtsp_server_adts_source.h" +#include "zm_rtsp_server_adts_fifo_source.h" #include #if HAVE_RTSP_SERVER @@ -15,16 +16,20 @@ // BaseServerMediaSubsession // --------------------------------- FramedSource* BaseServerMediaSubsession::createSource( - UsageEnvironment& env, FramedSource* inputSource, const std::string& format) + UsageEnvironment& env, + FramedSource* inputSource, + const std::string& format) { FramedSource* source = nullptr; - if ( format == "video/MP2T" ) { + if (format == "video/MP2T") { source = MPEG2TransportStreamFramer::createNew(env, inputSource); - } else if ( format == "video/H264" ) { - source = H264VideoStreamDiscreteFramer::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" ) { + else if (format == "video/H265") { source = H265VideoStreamDiscreteFramer::createNew(env, inputSource); } #endif @@ -49,27 +54,28 @@ RTPSink* BaseServerMediaSubsession::createSink( ) { RTPSink* sink = nullptr; - if ( format == "video/MP2T" ) { + if (format == "video/MP2T") { sink = SimpleRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, 90000, "video", "MP2T", 1, true, false); - } else if ( format == "video/H264" ) { + } else if (format == "video/H264") { sink = H264VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); - } else if ( format == "video/VP8" ) { + } else if (format == "video/VP8") { sink = VP8VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); } #if LIVEMEDIA_LIBRARY_VERSION_INT > 1414454400 - else if ( format == "video/VP9" ) { + else if (format == "video/VP9") { sink = VP9VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); - } else if ( format == "video/H265" ) { + } else if (format == "video/H265") { sink = H265VideoRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic); + } #endif - } else if ( format == "audio/AAC" ) { - ADTS_ZoneMinderDeviceSource *adts_source = (ADTS_ZoneMinderDeviceSource *)(m_replicator->inputSource()); + else if (format == "audio/AAC") { + ADTS_ZoneMinderFifoSource *adts_source = (ADTS_ZoneMinderFifoSource *)(m_replicator->inputSource()); sink = MPEG4GenericRTPSink::createNew(env, rtpGroupsock, rtpPayloadTypeIfDynamic, - adts_source->samplingFrequency(), + adts_source->getFrequency(), "audio", "AAC-hbr", adts_source->configStr(), - adts_source->numChannels() + adts_source->getChannels() ); } else { Error("unknown format"); @@ -83,24 +89,20 @@ RTPSink* BaseServerMediaSubsession::createSink( } char const* BaseServerMediaSubsession::getAuxLine( - ZoneMinderDeviceSource* source, + ZoneMinderFifoSource* source, unsigned char rtpPayloadType ) { const char* auxLine = nullptr; - if ( source ) { + if (source) { std::ostringstream os; os << "a=fmtp:" << int(rtpPayloadType) << " "; os << source->getAuxLine(); - os << "\r\n"; - int width = source->getWidth(); - int height = source->getHeight(); - if ( (width > 0) && (height>0) ) { - os << "a=x-dimensions:" << width << "," << height << "\r\n"; - } + //os << "\r\n"; auxLine = strdup(os.str().c_str()); - Debug(1, "auxLine: %s", auxLine); + Debug(1, "BaseServerMediaSubsession::auxLine: %s", auxLine); } else { - Error("No source auxLine: "); + Error("No source auxLine:"); + return ""; } return auxLine; } diff --git a/src/zm_rtsp_server_server_media_subsession.h b/src/zm_rtsp_server_server_media_subsession.h index 4b6166b44..d3780cd6e 100644 --- a/src/zm_rtsp_server_server_media_subsession.h +++ b/src/zm_rtsp_server_server_media_subsession.h @@ -11,6 +11,7 @@ #define ZM_RTSP_SERVER_SERVER_MEDIA_SUBSESSION_H #include "zm_config.h" +#include "zm_rtsp_server_fifo_source.h" #include #if HAVE_RTSP_SERVER @@ -36,7 +37,7 @@ class BaseServerMediaSubsession { FramedSource *source); char const* getAuxLine( - ZoneMinderDeviceSource* source, + ZoneMinderFifoSource* source, unsigned char rtpPayloadType); protected: diff --git a/src/zm_rtsp_server_thread.cpp b/src/zm_rtsp_server_thread.cpp index 1b5c2f61a..cbe7b905b 100644 --- a/src/zm_rtsp_server_thread.cpp +++ b/src/zm_rtsp_server_thread.cpp @@ -1,157 +1,128 @@ #include "zm_rtsp_server_thread.h" #include "zm_config.h" -#include "zm_rtsp_server_adts_source.h" -#include "zm_rtsp_server_h264_device_source.h" -#include "zm_rtsp_server_unicast_server_media_subsession.h" +#include "zm_logger.h" #if HAVE_RTSP_SERVER -#include -RTSPServerThread::RTSPServerThread(std::shared_ptr monitor) : - monitor_(std::move(monitor)), - terminate(0) +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"; - //unsigned int timeout = 65; - OutPacketBuffer::maxSize = 2000000; - - scheduler = BasicTaskScheduler::createNew(); - env = BasicUsageEnvironment::createNew(*scheduler); - authDB = nullptr; - //authDB = new UserAuthenticationDatabase("ZoneMinder"); - //authDB->addUserRecord("username1", "password1"); // replace these with real strings - - portNumBits rtspServerPortNum = config.min_rtsp_port + monitor_->Id(); - rtspServer = RTSPServer::createNew(*env, rtspServerPortNum, authDB); + // + eventLoop = std::make_shared(); + rtspServer = xop::RtspServer::Create(eventLoop.get()); if ( rtspServer == nullptr ) { - Fatal("Failed to create rtspServer at port %d", rtspServerPortNum); + Fatal("Failed to create rtspServer"); return; } - const char *prefix = rtspServer->rtspURLPrefix(); - Debug(1, "RTSP prefix is %s", prefix); - delete[] prefix; -} // end RTSPServerThread::RTSPServerThread + + thread_ = std::thread(&RTSPServerThread::Run, this); +} RTSPServerThread::~RTSPServerThread() { - if ( rtspServer ) { - Medium::close(rtspServer); - } // end if rtsp_server - while ( sources.size() ) { - FramedSource *source = sources.front(); - sources.pop_front(); - Medium::close(source); - } - env->reclaim(); - delete scheduler; + Stop(); + if (thread_.joinable()) + thread_.join(); + } -int RTSPServerThread::run() { - Debug(1, "RTSPServerThread::run()"); - if ( rtspServer ) - env->taskScheduler().doEventLoop(&terminate); // does not return - Debug(1, "RTSPServerThread::done()"); - return 0; -} // end in RTSPServerThread::run() - -void RTSPServerThread::stop() { - Debug(1, "RTSPServerThread::stop()"); - terminate = 1; - for ( std::list::iterator it = sources.begin(); it != sources.end(); ++it ) { - (*it)->stopGettingFrames(); - } -} // end RTSPServerThread::stop() - -bool RTSPServerThread::stopped() const { - return terminate ? true : false; -} // end RTSPServerThread::stopped() - -void RTSPServerThread::addStream(AVStream *video_stream, AVStream *audio_stream) { - if ( !rtspServer ) - return; - - int queueSize = 30; - bool repeatConfig = true; - bool muxTS = false; - ServerMediaSession *sms = nullptr; - - if ( video_stream ) { - StreamReplicator* videoReplicator = nullptr; - FramedSource *source = nullptr; - std::string rtpFormat = getRtpFormat(video_stream->codecpar->codec_id, false); - if ( rtpFormat.empty() ) { - Error("No streaming format"); - return; - } - Debug(1, "RTSP: format %s", rtpFormat.c_str()); - if ( rtpFormat == "video/H264" ) { - source = H264_ZoneMinderDeviceSource::createNew(*env, monitor_, video_stream, queueSize, repeatConfig, muxTS); - } else if ( rtpFormat == "video/H265" ) { - source = H265_ZoneMinderDeviceSource::createNew(*env, monitor_, video_stream, queueSize, repeatConfig, muxTS); +void RTSPServerThread::Run() { + Debug(1, "RTSPServerThread::Run()"); + if (rtspServer) { + while (!scheduler_watch_var_) { + //if (clients > 0) { + sleep(1); + //} } - if ( source == nullptr ) { - Error("Unable to create source"); + } + 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 { - videoReplicator = StreamReplicator::createNew(*env, source, false); + Warning("Unknown format in %s", fifo.c_str()); + } + if (source == nullptr) { + Error("Unable to create source"); } sources.push_back(source); - - // Create Unicast Session - if ( videoReplicator ) { - if ( !sms ) - sms = ServerMediaSession::createNew(*env, "streamname"); - sms->addSubsession(UnicastServerMediaSubsession::createNew(*env, videoReplicator, rtpFormat)); - } - } - if ( audio_stream ) { - StreamReplicator* replicator = nullptr; - FramedSource *source = nullptr; - std::string rtpFormat = getRtpFormat(audio_stream->codecpar->codec_id, false); - if ( rtpFormat == "audio/AAC" ) { - source = ADTS_ZoneMinderDeviceSource::createNew(*env, monitor_, audio_stream, queueSize); - Debug(1, "ADTS source %p", source); - } - if ( source ) { - replicator = StreamReplicator::createNew(*env, source, false /* deleteWhenLastReplicaDies */); - sources.push_back(source); - } - if ( replicator ) { - if ( !sms ) - sms = ServerMediaSession::createNew(*env, "streamname"); - sms->addSubsession(UnicastServerMediaSubsession::createNew(*env, replicator, rtpFormat)); - } } else { - Debug(1, "Not Adding audio stream"); + Debug(1, "Not Adding stream as fifo was empty"); } - if ( sms ) { - rtspServer->addServerMediaSession(sms); - char *url = rtspServer->rtspURL(sms); - Debug(1, "url is %s", url); - delete[] url; - } -} // end void addStream + return source; +} // end void addFifo -// ----------------------------------------- -// convert V4L2 pix format to RTP mime -// ----------------------------------------- -const std::string RTSPServerThread::getRtpFormat(AVCodecID codec_id, bool muxTS) { - if ( muxTS ) { - return "video/MP2T"; - } else { - switch ( codec_id ) { - case AV_CODEC_ID_H265 : return "video/H265"; - case AV_CODEC_ID_H264 : return "video/H264"; - //case PIX_FMT_MJPEG: rtpFormat = "video/JPEG"; break; - //case PIX_FMT_JPEG : rtpFormat = "video/JPEG"; break; - //case AV_PIX_FMT_VP8 : rtpFormat = "video/VP8" ; break; - //case AV_PIX_FMT_VP9 : rtpFormat = "video/VP9" ; break; - case AV_CODEC_ID_AAC : return "audio/AAC"; - default: break; - } - } - - return ""; -} #endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_thread.h b/src/zm_rtsp_server_thread.h index 57fc33bc9..393203831 100644 --- a/src/zm_rtsp_server_thread.h +++ b/src/zm_rtsp_server_thread.h @@ -3,41 +3,42 @@ #include "zm_config.h" #include "zm_ffmpeg.h" -#include "zm_thread.h" +#include "xop/RtspServer.h" + +#include "zm_rtsp_server_fifo_source.h" +#include #include #include #if HAVE_RTSP_SERVER -#include -#include class Monitor; -class RTSPServerThread : public Thread { +class RTSPServerThread { private: std::shared_ptr monitor_; - char terminate; - TaskScheduler* scheduler; - UsageEnvironment* env; - UserAuthenticationDatabase* authDB; + std::thread thread_; + std::atomic terminate_; + std::mutex scheduler_watch_var_mutex_; + char scheduler_watch_var_; - RTSPServer* rtspServer; - std::list sources; + std::shared_ptr eventLoop; + std::shared_ptr rtspServer; + + std::list sources; + int port; public: - explicit RTSPServerThread(std::shared_ptr monitor); + explicit RTSPServerThread(int port); ~RTSPServerThread(); - void addStream(AVStream *, AVStream *); - int run(); - void stop(); - bool stopped() const; - private: - const std::string getRtpFormat(AVCodecID codec, bool muxTS); - int addSession( - const std::string & sessionName, - const std::list & subSession - ); + 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 diff --git a/src/zm_rtsp_server_unicast_server_media_subsession.cpp b/src/zm_rtsp_server_unicast_server_media_subsession.cpp index 0b5566860..2376748d5 100644 --- a/src/zm_rtsp_server_unicast_server_media_subsession.cpp +++ b/src/zm_rtsp_server_unicast_server_media_subsession.cpp @@ -10,7 +10,7 @@ #include "zm_rtsp_server_unicast_server_media_subsession.h" #include "zm_config.h" -#include "zm_rtsp_server_device_source.h" +#include "zm_rtsp_server_fifo_source.h" #if HAVE_RTSP_SERVER // ----------------------------------------- @@ -45,6 +45,8 @@ RTPSink* UnicastServerMediaSubsession::createNewRTPSink( char const* UnicastServerMediaSubsession::getAuxSDPLine( RTPSink* rtpSink, FramedSource* inputSource ) { - return this->getAuxLine(dynamic_cast(m_replicator->inputSource()), rtpSink->rtpPayloadType()); + return this->getAuxLine( + dynamic_cast(m_replicator->inputSource()), + rtpSink->rtpPayloadType()); } #endif // HAVE_RTSP_SERVER diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index 247ce82cb..bdbc9e1d1 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -20,11 +20,9 @@ #include "zm_sdp.h" #include "zm_config.h" +#include "zm_exception.h" #include "zm_logger.h" -#if HAVE_LIBAVFORMAT - -#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 }, { 3, "GSM", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 }, @@ -62,51 +60,12 @@ SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = { { "AMR", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AMR_NB }, { "vnd.onvif.metadata", AVMEDIA_TYPE_DATA, AV_CODEC_ID_NONE } }; -#else -SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = { - { 0, "PCMU", CODEC_TYPE_AUDIO, CODEC_ID_PCM_MULAW, 8001, 1 }, - { 3, "GSM", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 4, "G723", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 5, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 6, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 16000, 1 }, - { 7, "LPC", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 8, "PCMA", CODEC_TYPE_AUDIO, CODEC_ID_PCM_ALAW, 8000, 1 }, - { 9, "G722", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 10, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 2 }, - { 11, "L16", CODEC_TYPE_AUDIO, CODEC_ID_PCM_S16BE, 44100, 1 }, - { 12, "QCELP", CODEC_TYPE_AUDIO, CODEC_ID_QCELP, 8000, 1 }, - { 13, "CN", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP2, -1, -1 }, - { 14, "MPA", CODEC_TYPE_AUDIO, CODEC_ID_MP3, -1, -1 }, - { 15, "G728", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 16, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 11025, 1 }, - { 17, "DVI4", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 22050, 1 }, - { 18, "G729", CODEC_TYPE_AUDIO, CODEC_ID_NONE, 8000, 1 }, - { 25, "CelB", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 }, - { 26, "JPEG", CODEC_TYPE_VIDEO, CODEC_ID_MJPEG, 90000, -1 }, - { 28, "nv", CODEC_TYPE_VIDEO, CODEC_ID_NONE, 90000, -1 }, - { 31, "H261", CODEC_TYPE_VIDEO, CODEC_ID_H261, 90000, -1 }, - { 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG1VIDEO, 90000, -1 }, - { 32, "MPV", CODEC_TYPE_VIDEO, CODEC_ID_MPEG2VIDEO, 90000, -1 }, - { 33, "MP2T", CODEC_TYPE_DATA, CODEC_ID_MPEG2TS, 90000, -1 }, - { 34, "H263", CODEC_TYPE_VIDEO, CODEC_ID_H263, 90000, -1 }, - { -1, "", CODEC_TYPE_UNKNOWN, CODEC_ID_NONE, -1, -1 } -}; - -SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = { - { "MP4V-ES", CODEC_TYPE_VIDEO, CODEC_ID_MPEG4 }, - { "mpeg4-generic", CODEC_TYPE_AUDIO, CODEC_ID_AAC }, - { "H264", CODEC_TYPE_VIDEO, CODEC_ID_H264 }, - { "AMR", CODEC_TYPE_AUDIO, CODEC_ID_AMR_NB }, - { "vnd.onvif.metadata", CODEC_TYPE_DATA, CODEC_ID_NONE } -}; -#endif 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]; @@ -115,7 +74,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]; @@ -128,7 +87,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]; @@ -164,7 +123,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() ) @@ -207,7 +166,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" ) { @@ -219,14 +178,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 ); @@ -236,13 +193,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); @@ -256,16 +213,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" ) { @@ -275,7 +232,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()); } } } @@ -293,13 +252,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 ) @@ -342,73 +301,44 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { //formatContext->nb_streams = mMediaList.size(); for ( unsigned int i = 0; i < mMediaList.size(); i++ ) { const MediaDescriptor *mediaDesc = mMediaList[i]; -#if !LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) - AVStream *stream = av_new_stream(formatContext, i); -#else AVStream *stream = avformat_new_stream(formatContext, nullptr); stream->id = i; -#endif -#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 std::string type = mediaDesc->getType(); Debug(1, "Looking for codec for %s payload type %d / %s", type.c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str()); -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( type == "video" ) codec_context->codec_type = AVMEDIA_TYPE_VIDEO; else if ( type == "audio" ) codec_context->codec_type = AVMEDIA_TYPE_AUDIO; else if ( type == "application" ) codec_context->codec_type = AVMEDIA_TYPE_DATA; -#else - if ( type == "video" ) - codec_context->codec_type = CODEC_TYPE_VIDEO; - else if ( type == "audio" ) - codec_context->codec_type = CODEC_TYPE_AUDIO; - else if ( type == "application" ) - codec_context->codec_type = CODEC_TYPE_DATA; -#endif else Warning("Unknown media_type %s", type.c_str()); -#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 ); -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - codec_name = std::string(smStaticPayloads[i].payloadName); -#else - strncpy(codec_context->codec_name, smStaticPayloads[i].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; + 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 ); + codec_name = std::string(smStaticPayloads[j].payloadName); + 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); -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - codec_name = std::string(smStaticPayloads[i].payloadName); -#else - strncpy(codec_context->codec_name, smDynamicPayloads[i].payloadName, sizeof(codec_context->codec_name)); -#endif - codec_context->codec_type = smDynamicPayloads[i].codecType; - codec_context->codec_id = smDynamicPayloads[i].codecId; + 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); + codec_name = std::string(smStaticPayloads[j].payloadName); + codec_context->codec_type = smDynamicPayloads[j].codecType; + codec_context->codec_id = smDynamicPayloads[j].codecId; codec_context->sample_rate = mediaDesc->getClock(); break; } @@ -416,12 +346,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { } /// end if static or dynamic -#if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) - if ( codec_name.empty() ) -#else - if ( !stream->codec->codec_name[0] ) -#endif - { + if (codec_name.empty()) { Warning( "Can't find payload details for %s payload type %d, name %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() ); } @@ -455,15 +380,9 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet)); Hexdump(4, (char *)decoded_packet, packet_size); if ( packet_size ) { - uint8_t *dest = - (uint8_t *)av_malloc(packet_size + sizeof(start_sequence) + - codec_context->extradata_size + -#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) - AV_INPUT_BUFFER_PADDING_SIZE -#else - FF_INPUT_BUFFER_PADDING_SIZE -#endif -); + uint8_t *dest = + (uint8_t *) av_malloc( + packet_size + sizeof(start_sequence) + codec_context->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); if ( dest ) { if ( codec_context->extradata_size ) { // av_realloc? @@ -475,11 +394,7 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size); memset(dest+codec_context->extradata_size+sizeof(start_sequence)+ packet_size, 0, -#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) AV_INPUT_BUFFER_PADDING_SIZE -#else - FF_INPUT_BUFFER_PADDING_SIZE -#endif ); codec_context->extradata= dest; @@ -491,12 +406,8 @@ 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; } - -#endif // HAVE_LIBAVFORMAT diff --git a/src/zm_sdp.h b/src/zm_sdp.h index e99c12920..68f7e30a0 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -32,11 +32,7 @@ protected: struct StaticPayloadDesc { int payloadType; const char payloadName[6]; -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) AVMediaType codecType; -#else - enum CodecType codecType; -#endif _AVCODECID codecId; int clockRate; int autoChannels; @@ -44,11 +40,7 @@ protected: struct DynamicPayloadDesc { const char payloadName[32]; -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) AVMediaType codecType; -#else - enum CodecType codecType; -#endif _AVCODECID codecId; //int clockRate; diff --git a/src/zm_sendfile.h b/src/zm_sendfile.h index 593a94833..fd72e2e61 100644 --- a/src/zm_sendfile.h +++ b/src/zm_sendfile.h @@ -1,10 +1,6 @@ #ifndef ZM_SENDFILE_H #define ZM_SENDFILE_H -#ifdef __cplusplus -extern "C" { -#endif - #ifdef HAVE_SENDFILE4_SUPPORT #include int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { @@ -37,8 +33,4 @@ int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) { #error "Your platform does not support sendfile. Sorry." #endif -#ifdef __cplusplus -} -#endif - -#endif +#endif // ZM_SENDFILE_H diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index 2d1315049..4107e96c8 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context) RETSIGTYPE zm_die_handler(int signal) #endif { + zm_terminate = true; Error("Got signal %d (%s), crashing", signal, strsignal(signal)); #if (defined(__i386__) || defined(__x86_64__)) // Get more information if available @@ -104,8 +105,7 @@ 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); diff --git a/src/zm_storage.cpp b/src/zm_storage.cpp index 5edc36e00..e53e79335 100644 --- a/src/zm_storage.cpp +++ b/src/zm_storage.cpp @@ -21,32 +21,33 @@ #include "zm_db.h" #include "zm_logger.h" +#include "zm_utils.h" #include Storage::Storage() : id(0) { - Warning("Instantiating default Storage Object. Should not happen."); - strcpy(name, "Default"); - if ( staticConfig.DIR_EVENTS[0] != '/' ) { + Warning("Instantiating default Storage Object. Should not happen."); + 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()); + 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) { - unsigned int index = 0; - id = atoi(dbrow[index++]); - strncpy(name, dbrow[index++], sizeof(name)-1); - strncpy(path, dbrow[index++], sizeof(path)-1); + unsigned int index = 0; + 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" ) { + if (scheme_str == "Deep") { scheme = DEEP; - } else if ( scheme_str == "Medium" ) { + } else if (scheme_str == "Medium") { scheme = MEDIUM; } else { scheme = SHALLOW; @@ -55,44 +56,42 @@ 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(p_id) { - - if ( id ) { - char sql[ZM_SQL_SML_BUFSIZ]; - 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", id, mysql_error(&dbconn)); - } else { - unsigned int index = 0; - id = atoi(dbrow[index++]); - strncpy(name, dbrow[index++], sizeof(name)-1); - strncpy(path, dbrow[index++], sizeof(path)-1); + if (id) { + std::string sql = stringtf("SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id); + Debug(2, "Loading Storage for %u using %s", id, sql.c_str()); + zmDbRow dbrow; + if (!dbrow.fetch(sql)) { + Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn)); + } else { + unsigned int index = 0; + 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" ) { + if (scheme_str == "Deep") { scheme = DEEP; - } else if ( scheme_str == "Medium" ) { + } else if (scheme_str == "Medium") { scheme = MEDIUM; } else { scheme = SHALLOW; } - Debug(1, "Loaded Storage area %d '%s'", id, name); - } - } - if ( !id ) { - if ( staticConfig.DIR_EVENTS[0] != '/' ) { + 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()); + 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); } - Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); - strcpy(name, "Default"); + Debug(1, "No id passed to Storage constructor. Using default path %s instead", path); + strcpy(name, "Default"); scheme = MEDIUM; scheme_str = "Medium"; - } + } } Storage::~Storage() { diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index ecbed9434..5255d3ab2 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -21,25 +21,27 @@ #include "zm_box.h" #include "zm_monitor.h" +#include #include #include #include -#include +#include + +constexpr Seconds StreamBase::MAX_STREAM_DELAY; +constexpr Milliseconds StreamBase::MAX_SLEEP; StreamBase::~StreamBase() { -#if HAVE_LIBAVCODEC - if ( vid_stream ) { + if (vid_stream) { delete vid_stream; vid_stream = nullptr; } -#endif closeComms(); } bool StreamBase::loadMonitor(int p_monitor_id) { monitor_id = p_monitor_id; - 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; } @@ -49,8 +51,13 @@ bool StreamBase::loadMonitor(int p_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; } @@ -58,18 +65,22 @@ 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 ) { + 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; } @@ -194,10 +205,10 @@ Image *StreamBase::prepareImage(Image *image) { last_crop = Box(); // Recalculate crop parameters, as %ges - int click_x = (last_crop.LoX() * 100 ) / last_act_image_width; // Initial crop offset from last image - click_x += ( x * 100 ) / last_virt_image_width; - int click_y = (last_crop.LoY() * 100 ) / last_act_image_height; // Initial crop offset from last image - click_y += ( y * 100 ) / last_virt_image_height; + int click_x = (last_crop.Lo().x_ * 100) / last_act_image_width; // Initial crop offset from last image + click_x += (x * 100) / last_virt_image_width; + int click_y = (last_crop.Lo().y_ * 100) / last_act_image_height; // Initial crop offset from last image + click_y += (y * 100) / last_virt_image_height; Debug(3, "Got adjusted click at %d%%,%d%%", click_x, click_y); // Convert the click locations to the current image pixels @@ -221,10 +232,10 @@ Image *StreamBase::prepareImage(Image *image) { hi_y = act_image_height - 1; lo_y = hi_y - (send_image_height - 1); } - last_crop = Box( lo_x, lo_y, hi_x, hi_y ); + last_crop = Box({lo_x, lo_y}, {hi_x, hi_y}); } // end if ( mag != last_mag || x != last_x || y != last_y ) - Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY()); + Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.Lo().x_, last_crop.Lo().y_, last_crop.Hi().x_, last_crop.Hi().y_); if ( !image_copied ) { static Image copy_image; copy_image.Assign(*image); @@ -243,42 +254,54 @@ Image *StreamBase::prepareImage(Image *image) { } // end Image *StreamBase::prepareImage(Image *image) bool StreamBase::sendTextFrame(const char *frame_text) { - Debug(2, "Sending %dx%d * %d text frame '%s'", - monitor->Width(), monitor->Height(), scale, frame_text); + int width = 640; + int height = 480; + int colours = ZM_COLOUR_RGB32; + int subpixelorder = ZM_SUBPIX_ORDER_RGBA; + int labelsize = 2; - Image image(monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder()); - image.Clear(); - image.Annotate(frame_text, image.centreCoord(frame_text, monitor->LabelSize()), monitor->LabelSize()); - - if ( scale != 100 ) { - image.Scale(scale); + if (monitor) { + width = monitor->Width(); + height = monitor->Height(); + colours = monitor->Colours(); + subpixelorder = monitor->SubpixelOrder(); + labelsize = monitor->LabelSize(); } -#if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) { - if ( !vid_stream ) { + Debug(2, "Sending %dx%dx%dx%d * %d scale text frame '%s'", + width, height, colours, subpixelorder, scale, frame_text); + + Image image(width, height, colours, subpixelorder); + image.Clear(); + image.Annotate(frame_text, image.centreCoord(frame_text, labelsize), labelsize); + + if (scale != 100) { + image.Scale(scale); + Debug(2, "Scaled to %dx%d", image.Width(), image.Height()); + } + if (type == STREAM_MPEG) { + if (!vid_stream) { vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, image.Colours(), image.SubpixelOrder(), image.Width(), image.Height()); fprintf(stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType()); vid_stream->OpenStream(); } /* double pts = */ vid_stream->EncodeFrame(image.Buffer(), image.Size()); - } else -#endif // HAVE_LIBAVCODEC - { + } else { static unsigned char buffer[ZM_MAX_IMAGE_SIZE]; int n_bytes = 0; image.EncodeJpeg(buffer, &n_bytes); + Debug(4, "Encoded to %d bytes", n_bytes); fputs("--" BOUNDARY "\r\nContent-Type: image/jpeg\r\n", stdout); fprintf(stdout, "Content-Length: %d\r\n\r\n", n_bytes); - if ( fwrite(buffer, n_bytes, 1, stdout) != 1 ) { + if (fwrite(buffer, n_bytes, 1, stdout) != 1) { Error("Unable to send stream text frame: %s", strerror(errno)); return false; } fputs("\r\n\r\n", stdout); fflush(stdout); } - last_frame_sent = TV_2_FLOAT(now); + last_frame_sent = now; return true; } @@ -349,7 +372,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)); @@ -363,7 +386,7 @@ void StreamBase::openComms() { strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)); rem_addr.sun_family = AF_UNIX; - gettimeofday(&last_comm_update, nullptr); + last_comm_update = std::chrono::system_clock::now(); } // end if connKey > 0 Debug(3, "comms open at %s", loc_sock_path); } // end void StreamBase::openComms() diff --git a/src/zm_stream.h b/src/zm_stream.h index 6d752689d..8650801ea 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -22,13 +22,13 @@ #include "zm_logger.h" #include "zm_mpeg.h" +#include "zm_time.h" #include #include class Image; class Monitor; -#define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) ) #define BOUNDARY "ZoneMinderFrame" class StreamBase { @@ -42,8 +42,8 @@ public: } StreamType; protected: - static const int MAX_STREAM_DELAY = 5; // Seconds - static const int MAX_SLEEP_USEC = 500000; // .5 Seconds + static constexpr Seconds MAX_STREAM_DELAY = Seconds(5); + static constexpr Milliseconds MAX_SLEEP = Milliseconds(500); static const StreamType DEFAULT_TYPE = STREAM_JPEG; enum { DEFAULT_RATE=ZM_RATE_BASE }; @@ -102,7 +102,6 @@ protected: int last_scale; int zoom; int last_zoom; - double maxfps; int bitrate; unsigned short last_x, last_y; unsigned short x, y; @@ -119,19 +118,23 @@ protected: bool paused; int step; - struct timeval now; - struct timeval last_comm_update; + SystemTimePoint now; + SystemTimePoint last_comm_update; + + double maxfps; + double base_fps; // Should be capturing fps, hence a rough target + double effective_fps; // Target fps after taking max_fps into account + double actual_fps; // sliding calculated actual streaming fps achieved + SystemTimePoint last_fps_update; + int frame_count; // Count of frames sent + int last_frame_count; // Used in calculating actual_fps from frame_count - last_frame_count - double base_fps; - double effective_fps; int frame_mod; - double last_frame_sent; - struct timeval last_frame_timestamp; + SystemTimePoint last_frame_sent; + SystemTimePoint last_frame_timestamp; -#if HAVE_LIBAVCODEC VideoStream *vid_stream; -#endif // HAVE_LIBAVCODEC CmdMsg msg; @@ -154,7 +157,6 @@ public: last_scale(DEFAULT_SCALE), zoom(DEFAULT_ZOOM), last_zoom(DEFAULT_ZOOM), - maxfps(DEFAULT_MAXFPS), bitrate(DEFAULT_BITRATE), last_x(0), last_y(0), @@ -166,7 +168,14 @@ public: sd(-1), lock_fd(0), paused(false), - step(0) + step(0), + maxfps(DEFAULT_MAXFPS), + base_fps(0.0), + effective_fps(0.0), + actual_fps(0.0), + frame_count(0), + last_frame_count(0), + frame_mod(1) { memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_addr, 0, sizeof(loc_addr)); @@ -174,15 +183,7 @@ public: memset(&rem_addr, 0, sizeof(rem_addr)); memset(&sock_path_lock, 0, sizeof(sock_path_lock)); - base_fps = 0.0; - effective_fps = 0.0; - frame_mod = 1; - -#if HAVE_LIBAVCODEC - vid_stream = 0; -#endif // HAVE_LIBAVCODEC - last_frame_sent = 0.0; - last_frame_timestamp = {}; + vid_stream = nullptr; msg = { 0, { 0 } }; } virtual ~StreamBase(); diff --git a/src/zm_swscale.cpp b/src/zm_swscale.cpp index 2390155c2..20b322ab9 100644 --- a/src/zm_swscale.cpp +++ b/src/zm_swscale.cpp @@ -22,30 +22,19 @@ #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"); } bool SWScale::init() { - /* Allocate AVFrame for the input */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) input_avframe = av_frame_alloc(); -#else - input_avframe = avcodec_alloc_frame(); -#endif - if ( input_avframe == nullptr ) { + if (!input_avframe) { Error("Failed allocating AVFrame for the input"); return false; } - /* Allocate AVFrame for the output */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) output_avframe = av_frame_alloc(); -#else - output_avframe = avcodec_alloc_frame(); -#endif - if ( output_avframe == nullptr ) { + if (!output_avframe) { Error("Failed allocating AVFrame for the output"); return false; } @@ -124,15 +113,15 @@ int SWScale::Convert( unsigned int new_width, unsigned int new_height ) { - Debug(1, "Convert: in_buffer %p in_buffer_size %d out_buffer %p size %d width %d height %d width %d height %d %d %d", + 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 ) { + if (in_buffer == nullptr) { Error("NULL Input buffer"); return -1; } - if ( out_buffer == nullptr ) { + if (out_buffer == nullptr) { Error("NULL output buffer"); return -1; } @@ -140,35 +129,40 @@ int SWScale::Convert( // 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) ) { + 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) ) { + 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; + int alignment = width % 32 ? 1 : 32; /* Check the buffer sizes */ 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: %d for %dx%d %d Available: %d", - needed_insize, width, height, in_pf, in_buffer_size); + if (needed_insize > in_buffer_size) { + Warning( + "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); } 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: %d Available: %d", needed_outsize, out_buffer_size); + 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; } @@ -177,29 +171,27 @@ int SWScale::Convert( width, height, in_pf, new_width, new_height, out_pf, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr); - if ( swscale_ctx == nullptr ) { + if (swscale_ctx == nullptr) { Error("Failed getting swscale context"); return -6; } + /* + input_avframe->format = in_pf; + input_avframe->width = width; + input_avframe->height = height; + output_avframe->format = out_pf; + output_avframe->width = new_width; + output_avframe->height = new_height; + */ /* 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, alignment) <= 0) { -#else - if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, - in_pf, width, height) <= 0) { -#endif Error("Failed filling input frame with input buffer"); return -7; } -#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, alignment) <= 0) { -#else - if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, new_width, - new_height) <= 0) { -#endif Error("Failed filling output frame with output buffer"); return -8; } @@ -270,10 +262,5 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si } 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 8e7e1fa19..c0d1e7120 100644 --- a/src/zm_swscale.h +++ b/src/zm_swscale.h @@ -7,7 +7,6 @@ class Image; /* SWScale wrapper class to make our life easier and reduce code reuse */ -#if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: SWScale(); @@ -32,6 +31,5 @@ class SWScale { unsigned int default_width; unsigned int default_height; }; -#endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL -#endif +#endif // ZM_SWSCALE_H diff --git a/src/zm_thread.cpp b/src/zm_thread.cpp deleted file mode 100644 index 63a8bdbb2..000000000 --- a/src/zm_thread.cpp +++ /dev/null @@ -1,317 +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::try_lock() { - 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))); - //Debug(3, "Lock"); -} - -bool Mutex::try_lock_for(int secs) { - struct timespec timeout = getTimeout(secs); - return pthread_mutex_timedlock(&mMutex, &timeout) == 0; -} - -bool Mutex::try_lock_for(double secs) { - struct timespec timeout = getTimeout(secs); - return pthread_mutex_timedlock(&mMutex, &timeout) == 0; -} - -void Mutex::unlock() { - if ( pthread_mutex_unlock(&mMutex) < 0 ) - throw ThreadException(stringtf("Unable to unlock pthread mutex: %s", strerror(errno))); - //Debug(3, "unLock"); -} - -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 ) { - Warning("You should really join the thread before destroying it"); - 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; - Debug(2,"Runnning"); - 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(4, "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(4, "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 4cdadb370..000000000 --- a/src/zm_thread.h +++ /dev/null @@ -1,266 +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 - -#include "zm_config.h" -#include "zm_exception.h" -#include "zm_utils.h" -#include -#include - -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_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; - - protected: - pthread_mutex_t mMutex; - - public: - Mutex(); - ~Mutex(); - - private: - pthread_mutex_t *getMutex() { - return &mMutex; - } - - public: - int try_lock(); - void lock(); - bool try_lock_for(int secs); - bool try_lock_for(double secs); - void unlock(); - bool locked(); -}; - -class RecursiveMutex : public Mutex { - 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_threaddata.cpp b/src/zm_threaddata.cpp deleted file mode 100644 index 6b25d5714..000000000 --- a/src/zm_threaddata.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// ZoneMinder Explicit Thread Template Class Instantiations, $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. -// - -template class ThreadData; -template class ThreadData; diff --git a/src/zm_time.cpp b/src/zm_time.cpp deleted file mode 100644 index 417ee2b2b..000000000 --- a/src/zm_time.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// ZoneMinder Time Functions & Definitions 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_time.h" - -// Blank diff --git a/src/zm_time.h b/src/zm_time.h index 5a54bea46..d3d5b95c5 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -20,189 +20,104 @@ #ifndef ZM_TIME_H #define ZM_TIME_H -#include +#include #include -// Structure used for storing the results of the subtraction -// of one struct timeval from another +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; -struct DeltaTimeval -{ - bool positive; - unsigned long delta; - unsigned long sec; - unsigned long fsec; - unsigned long prec; +// floating point seconds +typedef std::chrono::duration FPSeconds; + +typedef std::chrono::steady_clock::time_point TimePoint; +typedef std::chrono::system_clock::time_point SystemTimePoint; + +namespace zm { +namespace chrono { +namespace impl { + +template +struct posix_duration_cast; + +// chrono -> timeval caster +template +struct posix_duration_cast, timeval> { + static timeval cast(std::chrono::duration const &d) { + timeval tv = {}; + + Seconds const sec = std::chrono::duration_cast(d); + + tv.tv_sec = sec.count(); + tv.tv_usec = std::chrono::duration_cast(d - sec).count(); + + return tv; + } }; -#define DT_GRAN_1000000 1000000 -#define DT_PREC_6 DT_GRAN_1000000 -#define DT_GRAN_100000 100000 -#define DT_PREC_5 DT_GRAN_100000 -#define DT_GRAN_10000 10000 -#define DT_PREC_4 DT_GRAN_10000 -#define DT_GRAN_1000 1000 -#define DT_PREC_3 DT_GRAN_1000 -#define DT_GRAN_100 100 -#define DT_PREC_2 DT_GRAN_100 -#define DT_GRAN_10 10 -#define DT_PREC_1 DT_GRAN_10 - -#define DT_MAXGRAN DT_GRAN_1000000 - -// This obviously wouldn't work for massive deltas but as it's mostly -// for frames it will only usually be a fraction of a second or so -#define DELTA_TIMEVAL( result, time1, time2, precision ) \ -{ \ - int delta = (((time1).tv_sec-(time2).tv_sec)*(precision))+(((time1).tv_usec-(time2).tv_usec)/(DT_MAXGRAN/(precision))); \ - result.positive = (delta>=0); \ - result.delta = abs(delta); \ - result.sec = result.delta/(precision); \ - result.fsec = result.delta%(precision); \ - result.prec = (precision); \ -} - -#define TIMEVAL_INTERVAL( result, time1, time2, precision ) \ -{ \ - int delta = (((time1).tv_sec-(time2).tv_sec)*(precision))+(((time1).tv_usec-(time2).tv_usec)/(DT_MAXGRAN/(precision))); \ - result.positive = (delta>=0); \ - result.delta = abs(delta); \ - result.sec = result.delta/(precision); \ - result.fsec = result.delta%(precision); \ - result.prec = (precision); \ -} - -#define USEC_PER_SEC 1000000 -#define MSEC_PER_SEC 1000 - -/* -extern struct timeval tv; -*/ - -inline int tvDiffUsec( struct timeval first, struct timeval last ) -{ - return( (last.tv_sec - first.tv_sec) * USEC_PER_SEC) + ((USEC_PER_SEC + last.tv_usec - first.tv_usec) - USEC_PER_SEC ); -} - -inline int tvDiffUsec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffUsec( first, now ) ); -} - -inline int tvDiffMsec( struct timeval first, struct timeval last ) -{ - return( (last.tv_sec - first.tv_sec) * MSEC_PER_SEC) + (((MSEC_PER_SEC + last.tv_usec - first.tv_usec) / MSEC_PER_SEC) - MSEC_PER_SEC ); -} - -inline int tvDiffMsec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffMsec( first, now ) ); -} - -inline double tvDiffSec( struct timeval first, struct timeval last ) -{ - return( double(last.tv_sec - first.tv_sec) + double(((USEC_PER_SEC + last.tv_usec - first.tv_usec) - USEC_PER_SEC) / (1.0*USEC_PER_SEC) ) ); -} - -inline double tvDiffSec( struct timeval first ) -{ - struct timeval now; - gettimeofday( &now, nullptr ); - return( tvDiffSec( first, now ) ); -} - -inline struct timeval tvZero() -{ - struct timeval t = { 0, 0 }; - return( t ); -} - -inline int tvIsZero( const struct timeval t ) -{ - return( t.tv_sec == 0 && t.tv_usec == 0 ); -} - -inline int tvCmp( struct timeval t1, struct timeval t2 ) -{ - if ( t1.tv_sec < t2.tv_sec ) - return( -1 ); - if ( t1.tv_sec > t2.tv_sec ) - return( 1 ); - if ( t1.tv_usec < t2.tv_usec ) - return( -1 ); - if ( t1.tv_usec > t2.tv_usec ) - return( 1 ); - return( 0 ); -} - -inline int tvEq( struct timeval t1, struct timeval t2 ) -{ - return( t1.tv_sec == t2.tv_sec && t1.tv_usec == t2.tv_usec ); -} - -inline struct timeval tvNow( void ) -{ - struct timeval t; - gettimeofday( &t, nullptr ); - return( t ); -} - -inline struct timeval tvCheck( struct timeval &t ) -{ - if ( t.tv_usec >= USEC_PER_SEC ) - { - Warning( "Timestamp too large %ld.%ld\n", t.tv_sec, (long int) t.tv_usec ); - t.tv_sec += t.tv_usec / USEC_PER_SEC; - t.tv_usec %= USEC_PER_SEC; +// timeval -> chrono caster +template +struct posix_duration_cast> { + static std::chrono::duration cast(timeval const &tv) { + return std::chrono::duration_cast>( + Seconds(tv.tv_sec) + Microseconds(tv.tv_usec) + ); } - else if ( t.tv_usec < 0 ) - { - Warning( "Got negative timestamp %ld.%ld\n", t.tv_sec, (long int)t.tv_usec ); - t.tv_usec = 0; - } - return( t ); +}; } -// Add t2 to t1 -inline struct timeval tvAdd( struct timeval t1, struct timeval t2 ) -{ - tvCheck(t1); - tvCheck(t2); - t1.tv_sec += t2.tv_sec; - t1.tv_usec += t2.tv_usec; - if ( t1.tv_usec >= USEC_PER_SEC ) - { - t1.tv_sec++; - t1.tv_usec -= USEC_PER_SEC; - } - return( t1 ); +// chrono -> timeval +template +auto duration_cast(std::chrono::duration const &d) +-> typename std::enable_if::value, timeval>::type { + return impl::posix_duration_cast, timeval>::cast(d); } -// Subtract t2 from t1 -inline struct timeval tvSub( struct timeval t1, struct timeval t2 ) -{ - tvCheck(t1); - tvCheck(t2); - t1.tv_sec -= t2.tv_sec; - t1.tv_usec -= t2.tv_usec; - if ( t1.tv_usec < 0 ) - { - t1.tv_sec--; - t1.tv_usec += USEC_PER_SEC; - } - return( t1 ) ; +// timeval -> chrono +template +Duration duration_cast(timeval const &tv) { + return impl::posix_duration_cast::cast(tv); +} +} } -inline struct timeval tvMake( time_t sec, suseconds_t usec ) -{ - struct timeval t; - t.tv_sec = sec; - t.tv_usec = usec; - return( t ); -} +// +// This can be used for benchmarking. It will measure the time in between +// its constructor and destructor (or when you call Finish()) and add that +// duration to a microseconds clock. +// +class TimeSegmentAdder { + public: + TimeSegmentAdder(Microseconds &in_target) : + target_(in_target), + start_time_(std::chrono::steady_clock::now()), + finished_(false) { + } + + ~TimeSegmentAdder() { + Finish(); + } + + // Call this to stop the timer and add the timed duration to `target`. + void Finish() { + if (!finished_) { + const TimePoint end_time = std::chrono::steady_clock::now(); + target_ += (std::chrono::duration_cast(end_time - start_time_)); + } + finished_ = true; + } + + private: + // This is where we will add our duration to. + Microseconds &target_; + + // The time we started. + const TimePoint start_time_; + + // True when it has finished timing. + bool finished_; +}; #endif // ZM_TIME_H 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 f5db16f3a..000000000 --- a/src/zm_timer.h +++ /dev/null @@ -1,110 +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 - -#include "zm_exception.h" -#include "zm_thread.h" -#include "zm_utils.h" - -#ifdef HAVE_SYS_SYSCALL_H -#include -#endif // HAVE_SYS_SYSCALL_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 dfba0d51d..44a22df38 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -21,19 +21,10 @@ #include "zm_crypt.h" #include "zm_logger.h" +#include "zm_time.h" #include "zm_utils.h" #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_GCRYPT_H -#include -#elif HAVE_LIBCRYPTO -#include -#endif // HAVE_GCRYPT_H || HAVE_LIBCRYPTO - User::User() { id = 0; username[0] = password[0] = 0; @@ -54,7 +45,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())); } @@ -94,33 +85,17 @@ 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]; + std::string escaped_username = zmDbEscapeString(username); - // 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 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()); - 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)); - } - - 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); + if (!result) + return nullptr; if ( mysql_num_rows(result) == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row(result); @@ -141,7 +116,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 = ""; @@ -165,22 +140,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); + 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 ) { @@ -207,101 +173,69 @@ User *zmLoadTokenUser(std::string jwt_token_str, bool use_remote_addr) { // Function to validate an authentication string User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT -#ifdef HAVE_GCRYPT_H - // Special initialisation for libgcrypt - if ( !gcry_check_version(GCRYPT_VERSION) ) { - Fatal("Unable to initialise libgcrypt"); - } - gcry_control(GCRYCTL_DISABLE_SECMEM, 0); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); -#endif // HAVE_GCRYPT_H - const char *remote_addr = ""; - if ( use_remote_addr ) { + if (use_remote_addr) { remote_addr = getenv("REMOTE_ADDR"); - if ( !remote_addr ) { + if (!remote_addr) { Warning("Can't determine remote address, using null"); remote_addr = ""; } } Debug(1, "Attempting to authenticate user from auth string '%s', remote addr(%s)", auth, remote_addr); - 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"); + 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); + if (!result) return nullptr; - } + int n_users = mysql_num_rows(result); - if ( n_users < 1 ) { + if (n_users < 1) { mysql_free_result(result); Warning("Unable to authenticate user"); 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 ) { + SystemTimePoint now = std::chrono::system_clock::now(); + Hours hours = Hours(config.auth_hash_ttl); + + if (hours == Hours(0)) { Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); - hours = 2; + hours = Hours(2); } else { - Debug(1, "AUTH_HASH_TTL is %d, time is %d", hours, now); + Debug(1, "AUTH_HASH_TTL is %" PRIi64 " h, time is %" PRIi64 " s", + static_cast(Hours(hours).count()), + static_cast(std::chrono::duration_cast(now.time_since_epoch()).count())); } - char auth_key[512] = ""; - char auth_md5[32+1] = ""; - size_t md5len = 16; - unsigned char md5sum[md5len]; - const char * hex = "0123456789abcdef"; - while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { - const char *user = dbrow[1]; - const char *pass = dbrow[2]; + while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { + const char *username = dbrow[1]; + const char *password = dbrow[2]; - time_t our_now = now; - for ( unsigned int i = 0; i < hours; i++, our_now -= 3600 ) { - struct tm *now_tm = localtime(&our_now); + SystemTimePoint our_now = now; + tm now_tm = {}; + for (Hours i = Hours(0); i < hours; i++, our_now -= Hours(1)) { + time_t our_now_t = std::chrono::system_clock::to_time_t(our_now); + localtime_r(&our_now_t, &now_tm); - snprintf(auth_key, sizeof(auth_key)-1, "%s%s%s%s%d%d%d%d", - config.auth_hash_secret, - user, - pass, - remote_addr, - now_tm->tm_hour, - now_tm->tm_mday, - now_tm->tm_mon, - now_tm->tm_year); + std::string auth_key = stringtf("%s%s%s%s%d%d%d%d", + config.auth_hash_secret, + username, + password, + remote_addr, + 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); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5data = { (unsigned char *)auth_key, (unsigned int)strlen(auth_key) }; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5data, md5sum, &md5len); -#endif - unsigned char *md5sum_ptr = md5sum; - char *auth_md5_ptr = auth_md5; + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(auth_key); + std::string auth_md5 = ByteArrayToHexString(md5_digest); - for ( unsigned int j = 0; j < md5len; j++ ) { - *auth_md5_ptr++ = hex[(*md5sum_ptr>>4)&0xf]; - *auth_md5_ptr++ = hex[(*md5sum_ptr++)&0xf]; - } - *auth_md5_ptr = 0; + Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key.c_str(), auth_md5.c_str(), auth); - Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", - auth_key, auth_md5, auth); - - if ( !strcmp(auth, auth_md5) ) { + if (!strcmp(auth, auth_md5.c_str())) { // We have a match User *user = new User(dbrow); Debug(1, "Authenticated user '%s'", user->getUsername()); @@ -313,9 +247,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { } // end foreach hour } // end foreach user mysql_free_result(result); -#else // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT - Error("You need to build with gnutls or openssl to use hash based auth"); -#endif // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT + Debug(1, "No user found for auth_key %s", auth); return nullptr; } // end User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) diff --git a/src/zm_user.h b/src/zm_user.h index a2d96a477..a8b78b488 100644 --- a/src/zm_user.h +++ b/src/zm_user.h @@ -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 de5748e80..5da5509ff 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -21,7 +21,7 @@ #include "zm_config.h" #include "zm_logger.h" -#include +#include #include #include #include /* Definition of AT_* constants */ @@ -32,152 +32,158 @@ #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); - - tempString = tempBuffer; - - return tempString; -} - -const std::string stringtf(const std::string format, ...) { - va_list ap; - char tempBuffer[8192]; - std::string tempString; - - 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 ); -} - -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)); + 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; } - return elems; + + tokens.push_back(str.substr(start)); + + return tokens; } -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; +StringVector Split(const std::string &str, const std::string &delim, size_t limit) { + StringVector tokens; + size_t start = 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 stringtf(const char* format, ...) { + va_list args; + va_start(args, format); + va_list args2; + va_copy(args2, args); - if ( !base64_table[0] ) { + int size = vsnprintf(nullptr, 0, format, args) + 1; // Extra space for '\0' + va_end(args); + + if (size <= 0) { + throw std::runtime_error("Error during formatting."); + } + + std::unique_ptr buf(new char[size]); + vsnprintf(buf.get(), size, format, args2); + va_end(args2); + + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} + +std::string ByteArrayToHexString(nonstd::span bytes) { + static constexpr char lowercase_table[] = "0123456789abcdef"; + std::string buf; + buf.resize(2 * bytes.size()); + + const uint8 *srcPtr = bytes.data(); + char *dstPtr = &buf[0]; + + for (size_t i = 0; i < bytes.size(); ++i) { + uint8 c = *srcPtr++; + *dstPtr++ = lowercase_table[c >> 4]; + *dstPtr++ = lowercase_table[c & 0x0f]; + } + + return buf; +} + +std::string Base64Encode(const std::string &str) { + static char base64_table[64] = {'\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); @@ -195,82 +201,54 @@ 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; +std::string TimevalToString(timeval tv) { + tm now = {}; + std::array tm_buf = {}; - 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; + 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 ""; } - return items.size(); -} - -int pairsplit(const char* string, const char delim, std::string& name, std::string& value) { - if ( string == nullptr ) - return -1; - - if ( string[0] == 0 ) - return -2; - - 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__)) __builtin_cpu_init(); - - if ( __builtin_cpu_supports("avx2") ) { + if (__builtin_cpu_supports("avx2")) { sse_version = 52; /* AVX2 */ Debug(1, "Detected a x86\\x86-64 processor with AVX2"); - } else if ( __builtin_cpu_supports("avx") ) { + } else if (__builtin_cpu_supports("avx")) { sse_version = 51; /* AVX */ Debug(1, "Detected a x86\\x86-64 processor with AVX"); - } else if ( __builtin_cpu_supports("sse4.2") ) { + } 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 ( __builtin_cpu_supports("sse4.1") ) { + } 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 ( __builtin_cpu_supports("ssse3") ) { + } else if (__builtin_cpu_supports("ssse3")) { sse_version = 35; /* SSSE3 */ - Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); - } else if ( __builtin_cpu_supports("sse3") ) { + 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 ( __builtin_cpu_supports("sse2") ) { + } else if (__builtin_cpu_supports("sse2")) { sse_version = 20; /* SSE2 */ Debug(1, "Detected a x86\\x86-64 processor with SSE2"); - } else if ( __builtin_cpu_supports("sse") ) { + } 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 @@ -295,19 +273,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" @@ -337,7 +315,7 @@ 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 */ @@ -346,81 +324,111 @@ void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes) { 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 f29bae2e3..d5ee88202 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -20,69 +20,149 @@ #ifndef ZM_UTILS_H #define ZM_UTILS_H +#include "zm_define.h" +#include #include #include +#include +#include #include -#include +#include "span.hpp" +#include #include +#include #include + +#ifdef NDEBUG +#define ASSERT(x) do { (void) sizeof(x); } while (0) +#else +#include +#define ASSERT(x) assert(x) +#endif + 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 std::string StringToUpper(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; +} -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); +inline bool StartsWith(const std::string &haystack, const std::string &needle) { + return (haystack.substr(0, needle.length()) == needle); +} -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); +__attribute__((format(printf, 1, 2))) +std::string stringtf(const char* format, ...); -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 ByteArrayToHexString(nonstd::span bytes); + +std::string Base64Encode(const std::string &str); + +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) +namespace zm { +// C++14 std::make_unique (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; + +// C++17 std::clamp (TODO: remove this once C++17 is supported) +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 zm::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; +// C++17 std::data (TODO: remove this once C++17 is supported) +template +constexpr auto data(C &c) -> decltype(c.data()) { return c.data(); } -typedef std::chrono::steady_clock::time_point TimePoint; -typedef std::chrono::system_clock::time_point SystemTimePoint; +template +constexpr auto data(C const &c) -> decltype(c.data()) { return c.data(); } + +template +constexpr T *data(T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(const T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(std::initializer_list l) noexcept { return l.begin(); } + +// C++17 std::size (TODO: remove this once C++17 is supported) +template +constexpr auto size(const C &c) -> decltype(c.size()) { return c.size(); } + +template +constexpr std::size_t size(const T(&)[N]) noexcept { return N; } +} + +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_vector2.h b/src/zm_vector2.h new file mode 100644 index 000000000..c7630ad36 --- /dev/null +++ b/src/zm_vector2.h @@ -0,0 +1,80 @@ +// +// ZoneMinder Coordinate 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_VECTOR2_H +#define ZM_VECTOR2_H + +#include "zm_define.h" +#include +#include + +// +// Class used for storing an x,y pair, i.e. a coordinate/vector +// +class Vector2 { + public: + Vector2() : x_(0), y_(0) {} + Vector2(int32 x, int32 y) : x_(x), y_(y) {} + + static Vector2 Inf() { + static const Vector2 inf = {std::numeric_limits::max(), std::numeric_limits::max()}; + return inf; + } + + bool operator==(const Vector2 &rhs) const { return (x_ == rhs.x_ && y_ == rhs.y_); } + bool operator!=(const Vector2 &rhs) const { return (x_ != rhs.x_ || y_ != rhs.y_); } + + // These operators are not idiomatic. If lexicographic comparison is needed, it should be implemented separately. + bool operator>(const Vector2 &rhs) const = delete; + bool operator>=(const Vector2 &rhs) const = delete; + bool operator<(const Vector2 &rhs) const = delete; + bool operator<=(const Vector2 &rhs) const = delete; + + Vector2 operator+(const Vector2 &rhs) const { + return {x_ + rhs.x_, y_ + rhs.y_}; + } + Vector2 operator-(const Vector2 &rhs) const { + return {x_ - rhs.x_, y_ - rhs.y_}; + } + Vector2 operator*(double rhs) const { + return {static_cast(std::lround(x_ * rhs)), static_cast(std::lround(y_ * rhs))}; + } + + Vector2 &operator+=(const Vector2 &rhs) { + x_ += rhs.x_; + y_ += rhs.y_; + return *this; + } + Vector2 &operator-=(const Vector2 &rhs) { + x_ -= rhs.x_; + y_ -= rhs.y_; + return *this; + } + + // Calculated the determinant of the 2x2 matrix as given by [[x_, y_], [v.x_y, v.y_]] + int32 Determinant(Vector2 const &v) const { + return (x_ * v.y_) - (y_ * v.x_); + } + + public: + int32 x_; + int32 y_; +}; + +#endif // ZM_VECTOR2_H diff --git a/src/zm_video.cpp b/src/zm_video.cpp deleted file mode 100644 index c2a9e161e..000000000 --- a/src/zm_video.cpp +++ /dev/null @@ -1,588 +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_video.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 %s: Invalid pair", lineno, line.c_str()); - 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 89e5e33af..000000000 --- a/src/zm_video.h +++ /dev/null @@ -1,171 +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_buffer.h" -#include "zm_config.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 0a3cc6002..ad401b22c 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -22,17 +22,40 @@ #include "zm_logger.h" #include "zm_monitor.h" +#include "zm_time.h" extern "C" { -#include "libavutil/time.h" +#include } +/* + 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[] = { - { AV_CODEC_ID_H264, "h264", "h264_vaapi", AV_PIX_FMT_NV12 }, - { AV_CODEC_ID_H264, "h264", "h264_omx", AV_PIX_FMT_YUV420P }, - { AV_CODEC_ID_H264, "h264", "h264", AV_PIX_FMT_YUV420P }, - { AV_CODEC_ID_H264, "h264", "libx264", AV_PIX_FMT_YUV420P }, - { AV_CODEC_ID_MJPEG, "mjpeg", "mjpeg", AV_PIX_FMT_YUVJ422P }, +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) + { 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( @@ -44,14 +67,12 @@ VideoStore::VideoStore( AVCodecContext *p_audio_in_ctx, Monitor *p_monitor ) : + chosen_codec_data(nullptr), monitor(p_monitor), out_format(nullptr), oc(nullptr), video_out_stream(nullptr), audio_out_stream(nullptr), - video_in_stream_index(-1), - audio_in_stream_index(-1), - video_out_codec(nullptr), video_in_ctx(p_video_in_ctx), video_out_ctx(nullptr), video_in_stream(p_video_in_stream), @@ -63,18 +84,16 @@ VideoStore::VideoStore( video_in_frame(nullptr), in_frame(nullptr), out_frame(nullptr), + hw_frame(nullptr), packets_written(0), frame_count(0), -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) + hw_device_ctx(nullptr), 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_pts(0), video_first_dts(0), audio_first_pts(0), audio_first_dts(0), @@ -92,7 +111,7 @@ 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 ) { + if (ret < 0) { Warning( "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", @@ -100,9 +119,9 @@ bool VideoStore::open() { } // Couldn't deduce format from filename, trying from format name - if ( !oc ) { + if (!oc) { avformat_alloc_output_context2(&oc, nullptr, format, filename); - if ( !oc ) { + if (!oc) { Error( "Could not create video storage stream %s as no out ctx" " could not be assigned based on filename or format %s", @@ -113,87 +132,158 @@ bool VideoStore::open() { AVDictionary *pmetadata = nullptr; ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); - if ( ret < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + 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 + AVCodec *video_out_codec = nullptr; - if ( video_in_stream ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVDictionary *opts = nullptr; + 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'", 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); + } + } + av_dict_free(&opts); + + if (video_in_stream) { zm_dump_codecpar(video_in_stream->codecpar); -#endif - video_in_stream_index = video_in_stream->index; - 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"); + if (monitor->GetOptVideoWriter() == Monitor::PASSTHROUGH) { + video_out_stream = avformat_new_stream(oc, nullptr); + if (!video_out_stream) { + Error("Unable to create video out stream"); 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 ) { + avcodec_parameters_copy(video_out_stream->codecpar, video_in_stream->codecpar); + zm_dump_codecpar(video_out_stream->codecpar); + + video_out_stream->avg_frame_rate = video_in_stream->avg_frame_rate; + // 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) { + 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) { + 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) { + 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 + + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); + if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) { + av_dict_set(&opts, "new_extradata", nullptr, 0); + // Special flag to tell us to open a codec to get new extraflags to fix weird h265 + video_out_codec = avcodec_find_encoder(video_in_stream->codecpar->codec_id); + if (video_out_codec) { + video_out_ctx = avcodec_alloc_context3(video_out_codec); + ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); + + 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) { + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + 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; + } + video_out_ctx->bit_rate = video_in_ctx->bit_rate; + video_out_ctx->gop_size = video_in_ctx->gop_size; + video_out_ctx->has_b_frames = video_in_ctx->has_b_frames; + video_out_ctx->max_b_frames = video_in_ctx->max_b_frames; + video_out_ctx->qmin = video_in_ctx->qmin; + video_out_ctx->qmax = video_in_ctx->qmax; + + if (!av_dict_get(opts, "crf", nullptr, AV_DICT_MATCH_CASE)) { + if (av_dict_set(&opts, "crf", "23", 0)<0) + Warning("Can't set crf to 23"); + } + + 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; + } + } // end if video_out_codec + + ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); + if (ret < 0) { + Error("Could not initialize stream parameteres"); + } + av_dict_free(&opts); + } // end if extradata_entry + } else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { int wanted_codec = monitor->OutputCodec(); - if ( !wanted_codec ) { + if (!wanted_codec) { // default to h264 - Debug(2, "Defaulting to H264"); - wanted_codec = AV_CODEC_ID_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 { - Debug(2, "Codec wanted %d", wanted_codec); + 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(); - for ( unsigned int i = 0; i < sizeof(codec_data) / sizeof(*codec_data); i++ ) { - if ( wanted_encoder != "" and wanted_encoder != "auto" ) { - if ( wanted_encoder != codec_data[i].codec_name ) { + 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 ( codec_data[i].codec_id != wanted_codec ) { - Debug(1, "Not the right codec %d != %d", codec_data[i].codec_id, wanted_codec); + 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 ) { + 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) + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { 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].pix_fmt; + 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 @@ -201,35 +291,60 @@ bool VideoStore::open() { video_out_ctx->height = monitor->Height(); video_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO; - if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { + 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 ) { + } 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 ) { + } 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; } - - 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)) != NULL ) { - Debug(3, "Encoder Option %s=%s", e->key, e->value); +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) + 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); + if (0>ret) { + Error("Failed to create hwdevice_ctx"); + continue; } - } - if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { - if ( wanted_encoder != "" and wanted_encoder != "auto" ) { + 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."); + continue; + } + 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); + av_buffer_unref(&hw_device_ctx); + } // end if hwdevice_type != NONE +#endif + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); + 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() @@ -244,103 +359,60 @@ bool VideoStore::open() { } AVDictionaryEntry *e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); } - //av_dict_free(&opts); - if ( video_out_codec ) break; - avcodec_free_context(&video_out_ctx); - } // end foreach codec - - if ( !video_out_codec ) { - Error("Can't open video codec!"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + av_dict_free(&opts); + if (video_out_codec) { + break; + } // We allocate and copy in newer ffmpeg, so need to free it avcodec_free_context(&video_out_ctx); -#endif - //video_out_ctx = nullptr; + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + } + } // end foreach codec + if (!video_out_codec) { + Error("Can't open video codec!"); return false; - } // end if can't open codec + } // end if can't open codec Debug(2, "Success opening codec"); - } // end if copying or transcoding - zm_dump_codec(video_out_ctx); + + video_out_stream = avformat_new_stream(oc, nullptr); + ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); + if (ret < 0) { + Error("Could not initialize stream parameteres"); + return false; + } + } // end if copying or transcoding } // 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 false; - } max_stream_index = video_out_stream->index; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); - if ( ret < 0 ) { - Error("Could not initialize stream parameteres"); - return false; - } -#else - avcodec_copy_context(video_out_stream->codec, video_out_ctx); -#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 ) { - 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 ) { - 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 ) { - 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; - if ( audio_in_stream and audio_in_ctx ) { + if (audio_in_stream and audio_in_ctx) { Debug(2, "Have audio_in_stream %p", audio_in_stream); - audio_in_stream_index = audio_in_stream->index; - - if ( -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_in_stream->codecpar->codec_id -#else - audio_in_stream->codec->codec_id -#endif - != AV_CODEC_ID_AAC - ) { + if (CODEC(audio_in_stream)->codec_id != AV_CODEC_ID_AAC) { audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if ( !audio_out_codec ) { + if (!audio_out_codec) { Error("Could not find codec for AAC"); } 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); + 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_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { + if (!audio_out_ctx) { Error("could not allocate codec ctx for AAC"); return false; } -#else - audio_out_ctx = audio_out_stream->codec; -#endif + audio_out_stream = avformat_new_stream(oc, audio_out_codec); audio_out_stream->time_base = audio_in_stream->time_base; - if ( !setup_resampler() ) { + if (!setup_resampler()) { return false; } } // end if found AAC codec @@ -350,16 +422,15 @@ bool VideoStore::open() { // 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 ) { + if (!audio_out_stream) { Error("Could not allocate new stream"); return false; } audio_out_stream->time_base = audio_in_stream->time_base; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Just use the ctx to copy the parameters over audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { + if (!audio_out_ctx) { Error("Could not allocate new output_context"); return false; } @@ -370,29 +441,18 @@ bool VideoStore::open() { // Copy params from instream to ctx ret = avcodec_parameters_to_context( audio_out_ctx, audio_in_stream->codecpar); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy audio params to ctx %s", av_make_error_string(ret).c_str()); } ret = avcodec_parameters_from_context( audio_out_stream->codecpar, audio_out_ctx); - if ( ret < 0 ) { + if (ret < 0) { Error("Unable to copy audio params to stream %s", av_make_error_string(ret).c_str()); } -#else - audio_out_ctx = audio_out_stream->codec; - ret = avcodec_copy_context(audio_out_ctx, audio_in_stream->codec); - if ( ret < 0 ) { - Error("Unable to copy audio ctx %s", - av_make_error_string(ret).c_str()); - audio_out_stream = nullptr; - return false; - } // end if - audio_out_ctx->codec_tag = 0; -#endif - if ( audio_out_ctx->channels > 1 ) { + if (audio_out_ctx->channels > 1) { Warning("Audio isn't mono, changing it."); audio_out_ctx->channels = 1; } else { @@ -400,12 +460,8 @@ bool VideoStore::open() { } } // end if is AAC - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { -#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; -#else - audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; -#endif } // We will assume that subsequent stream allocations will increase the index @@ -414,14 +470,14 @@ bool VideoStore::open() { //max_stream_index is 0-based, so add 1 next_dts = new int64_t[max_stream_index+1]; - for ( int i = 0; i <= max_stream_index; i++ ) { + for (int i = 0; i <= max_stream_index; i++) { next_dts[i] = 0; } /* open the out file, if needed */ - if ( !(out_format->flags & AVFMT_NOFILE) ) { + if (!(out_format->flags & AVFMT_NOFILE)) { ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, nullptr, nullptr); - if ( ret < 0 ) { + if (ret < 0) { Error("Could not open out file '%s': %s", filename, av_make_error_string(ret).c_str()); return false; @@ -429,39 +485,32 @@ bool VideoStore::open() { } zm_dump_stream_format(oc, 0, 0, 1); - if ( audio_out_stream ) zm_dump_stream_format(oc, 1, 0, 1); - - AVDictionary *opts = nullptr; - - 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()); - } + if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE); - if ( !movflags_entry ) { + if (!movflags_entry) { Debug(1, "setting movflags to frag_keyframe+empty_moov"); // Shiboleth reports that this may break seeking in mp4 before it downloads av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); } else { Debug(1, "using movflags %s", movflags_entry->value); } - if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { + if ((ret = avformat_write_header(oc, &opts)) < 0) { Warning("Unable to set movflags trying with defaults."); ret = avformat_write_header(oc, nullptr); - } else if ( av_dict_count(opts) != 0 ) { + } else if (av_dict_count(opts) != 0) { Info("some options not used, turn on debugging for a list."); AVDictionaryEntry *e = nullptr; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr ) { + while ((e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != nullptr) { Debug(1, "Encoder Option %s=>%s", e->key, e->value); - if ( !e->value ) { + if (!e->value) { av_dict_set(&opts, e->key, nullptr, 0); } } } - if ( opts ) av_dict_free(&opts); - if ( ret < 0 ) { + 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()); avio_closep(&oc->pb); @@ -474,7 +523,6 @@ bool VideoStore::open() { } // end bool VideoStore::open() 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; @@ -484,15 +532,9 @@ void VideoStore::flush_codecs() { av_init_packet(&pkt); // 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 - ) ) { + if (video_out_ctx && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) { // Put encoder into flushing mode - while ( (ret = zm_send_frame_receive_packet(video_out_ctx, nullptr, pkt) ) > 0 ) { + while ((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); @@ -502,7 +544,7 @@ void VideoStore::flush_codecs() { 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. @@ -510,13 +552,13 @@ void VideoStore::flush_codecs() { /* * 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) ) { + 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) ) { + 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 ) { + 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); @@ -525,11 +567,10 @@ void VideoStore::flush_codecs() { } } // end if data returned from 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 ) { + 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. */ @@ -537,8 +578,8 @@ void VideoStore::flush_codecs() { 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) ) { + 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, @@ -550,13 +591,11 @@ void VideoStore::flush_codecs() { } // 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 ( 0 >= zm_receive_packet(audio_out_ctx, pkt) ) { + while (true) { + if (0 >= zm_receive_packet(audio_out_ctx, pkt)) { Debug(1, "No more packets"); break; } @@ -571,33 +610,34 @@ void VideoStore::flush_codecs() { } // end flush_codecs VideoStore::~VideoStore() { - if ( oc->pb ) { + if (oc->pb) { flush_codecs(); // Flush Queues - Debug(1, "Flushing interleaved queues"); + Debug(4, "Flushing interleaved queues"); av_interleaved_write_frame(oc, nullptr); Debug(1, "Writing trailer"); /* Write the trailer before close */ - if ( int rc = av_write_trailer(oc) ) { + int rc; + if ((rc = av_write_trailer(oc)) < 0) { Error("Error writing trailer %s", av_err2str(rc)); } else { Debug(3, "Success Writing trailer"); } // When will we not be using a file ? - if ( !(out_format->flags & AVFMT_NOFILE) ) { + if (!(out_format->flags & AVFMT_NOFILE)) { /* Close the out file. */ - Debug(2, "Closing"); - if ( int rc = avio_close(oc->pb) ) { + Debug(4, "Closing"); + if ((rc = avio_close(oc->pb)) < 0) { Error("Error closing avio %s", av_err2str(rc)); } } else { Debug(3, "Not closing avio because we are not writing to a file."); } oc->pb = nullptr; - } // end if oc->pb + } // 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 @@ -605,56 +645,46 @@ VideoStore::~VideoStore() { // Just do a file open/close/writeheader/etc. // What if we were only doing audio recording? - if ( video_out_stream ) { - video_in_ctx = nullptr; + video_in_ctx = nullptr; - Debug(4, "Freeing video_out_ctx"); + if (video_out_ctx) { + avcodec_close(video_out_ctx); + Debug(3, "Freeing video_out_ctx"); avcodec_free_context(&video_out_ctx); - } // end if video_out_stream + if (hw_device_ctx) { + Debug(3, "Freeing hw_device_ctx"); + av_buffer_unref(&hw_device_ctx); + } + } - if ( audio_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(&audio_in_ctx); -#endif - //Debug(4, "Success freeing audio_in_ctx"); + if (audio_out_stream) { audio_in_codec = nullptr; - if ( audio_out_ctx ) { + if (audio_out_ctx) { Debug(4, "Success closing audio_out_ctx"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_close(audio_out_ctx); avcodec_free_context(&audio_out_ctx); -#endif } -#if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) - if ( resample_ctx ) { - if ( fifo ) { + if (resample_ctx) { + if (fifo) { av_audio_fifo_free(fifo); fifo = nullptr; } - #if defined(HAVE_LIBSWRESAMPLE) swr_free(&resample_ctx); - #else - #if defined(HAVE_LIBAVRESAMPLE) - avresample_close(resample_ctx); - avresample_free(&resample_ctx); - #endif - #endif } - if ( in_frame ) { + if (in_frame) { av_frame_free(&in_frame); in_frame = nullptr; } - if ( out_frame ) { + if (out_frame) { av_frame_free(&out_frame); out_frame = nullptr; } - if ( converted_in_samples ) { + if (converted_in_samples) { av_free(converted_in_samples); converted_in_samples = nullptr; } -#endif } // end if audio_out_stream Debug(4, "free context"); @@ -665,42 +695,21 @@ VideoStore::~VideoStore() { } // 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"); - return false; -#else int ret; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Newer ffmpeg wants to keep everything separate... so have to lookup our own // decoder, can't reuse the one from the camera. - audio_in_codec = - avcodec_find_decoder(audio_in_stream->codecpar->codec_id); + audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); audio_in_ctx = avcodec_alloc_context3(audio_in_codec); // Copy params from instream to ctx - ret = avcodec_parameters_to_context( - audio_in_ctx, audio_in_stream->codecpar); - if ( ret < 0 ) { + ret = avcodec_parameters_to_context(audio_in_ctx, audio_in_stream->codecpar); + if (ret < 0) { Error("Unable to copy audio params to ctx %s", av_make_error_string(ret).c_str()); } -#else -// 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 ) { - audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); - } - if ( !audio_in_codec ) { - return false; - } -#endif - // if the codec is already open, nothing is done. - if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, nullptr)) < 0 ) { + if ((ret = avcodec_open2(audio_in_ctx, audio_in_codec, nullptr)) < 0) { Error("Can't open audio in codec!"); return false; } @@ -708,7 +717,7 @@ bool VideoStore::setup_resampler() { Debug(2, "Got something other than AAC (%s)", audio_in_codec->name); // Some formats (i.e. WAV) do not produce the proper channel layout - if ( audio_in_ctx->channel_layout == 0 ) { + if (audio_in_ctx->channel_layout == 0) { Debug(2, "Setting input channel layout to mono"); // Perhaps we should not be modifying the audio_in_ctx.... audio_in_ctx->channel_layout = av_get_channel_layout("mono"); @@ -721,36 +730,34 @@ bool VideoStore::setup_resampler() { 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)", + if (!audio_out_ctx->channel_layout) { + Debug(3, "Correcting channel layout from (%" PRIi64 ") to (%" PRIi64 ")", audio_out_ctx->channel_layout, av_get_default_channel_layout(audio_out_ctx->channels) ); audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels); } -#endif - if ( audio_out_codec->supported_samplerates ) { + + if (audio_out_codec->supported_samplerates) { int found = 0; - for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++ ) { - if ( audio_out_ctx->sample_rate == - audio_out_codec->supported_samplerates[i] ) { + for (unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++) { + if (audio_out_ctx->sample_rate == + audio_out_codec->supported_samplerates[i]) { found = 1; break; } } - if ( found ) { + if (found) { 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_ctx->sample_rate = audio_out_codec->supported_samplerates[0]; Debug(1, "Sample rate is no good, setting to (%d)", audio_out_codec->supported_samplerates[0]); } } /* check that the encoder supports s16 pcm in */ - if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) { + if (!check_sample_fmt(audio_out_codec, 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; @@ -761,12 +768,12 @@ bool VideoStore::setup_resampler() { AVDictionary *opts = nullptr; // Needed to allow AAC - if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { + if ((ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0) { Error("Couldn't set experimental"); } ret = avcodec_open2(audio_out_ctx, audio_out_codec, &opts); av_dict_free(&opts); - if ( ret < 0 ) { + if (ret < 0) { Error("could not open codec (%d) (%s)", ret, av_make_error_string(ret).c_str()); audio_out_codec = nullptr; @@ -777,15 +784,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) - if ( (ret = avcodec_parameters_from_context( - audio_out_stream->codecpar, - audio_out_ctx)) < 0 ) { + if ((ret = avcodec_parameters_from_context(audio_out_stream->codecpar, audio_out_ctx)) < 0) { Error("Could not initialize stream parameteres"); return false; } zm_dump_codecpar(audio_out_stream->codecpar); -#endif Debug(3, "Time bases: AUDIO in stream (%d/%d) in codec: (%d/%d) out " @@ -796,36 +799,24 @@ 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 context bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) 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); + "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 (%d) sample_rate(%d) channels(%d) fmt(%d) " - "layout(%d) 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(%d) 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 + "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); /** Create a new frame to store the audio samples. */ - if ( ! in_frame ) { + if (!in_frame) { if (!(in_frame = zm_av_frame_alloc())) { Error("Could not allocate in frame"); return false; @@ -833,20 +824,19 @@ bool VideoStore::setup_resampler() { } /** Create a new frame to store the audio samples. */ - if ( !(out_frame = zm_av_frame_alloc()) ) { + if (!(out_frame = zm_av_frame_alloc())) { Error("Could not allocate out frame"); av_frame_free(&in_frame); return false; } out_frame->sample_rate = audio_out_ctx->sample_rate; - if ( !(fifo = av_audio_fifo_alloc( + if (!(fifo = av_audio_fifo_alloc( audio_out_ctx->sample_fmt, - audio_out_ctx->channels, 1)) ) { + audio_out_ctx->channels, 1))) { Error("Could not allocate FIFO"); return false; } -#if defined(HAVE_LIBSWRESAMPLE) resample_ctx = swr_alloc_set_opts(nullptr, audio_out_ctx->channel_layout, audio_out_ctx->sample_fmt, @@ -855,13 +845,13 @@ bool VideoStore::setup_resampler() { audio_in_ctx->sample_fmt, audio_in_ctx->sample_rate, 0, nullptr); - if ( !resample_ctx ) { + if (!resample_ctx) { Error("Could not allocate resample context"); av_frame_free(&in_frame); av_frame_free(&out_frame); return false; } - if ( (ret = swr_init(resample_ctx)) < 0 ) { + if ((ret = swr_init(resample_ctx)) < 0) { Error("Could not open resampler"); av_frame_free(&in_frame); av_frame_free(&out_frame); @@ -869,49 +859,10 @@ bool VideoStore::setup_resampler() { return false; } Debug(1,"Success setting up SWRESAMPLE"); -#else -#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); - av_frame_free(&out_frame); - return false; - } - - av_opt_set_int(resample_ctx, "in_channel_layout", - audio_in_ctx->channel_layout, 0); - av_opt_set_int(resample_ctx, "in_sample_fmt", - audio_in_ctx->sample_fmt, 0); - av_opt_set_int(resample_ctx, "in_sample_rate", - audio_in_ctx->sample_rate, 0); - av_opt_set_int(resample_ctx, "in_channels", - audio_in_ctx->channels, 0); - av_opt_set_int(resample_ctx, "out_channel_layout", - audio_in_ctx->channel_layout, 0); - av_opt_set_int(resample_ctx, "out_sample_fmt", - audio_out_ctx->sample_fmt, 0); - av_opt_set_int(resample_ctx, "out_sample_rate", - audio_out_ctx->sample_rate, 0); - av_opt_set_int(resample_ctx, "out_channels", - audio_out_ctx->channels, 0); - - if ( (ret = avresample_open(resample_ctx)) < 0 ) { - Error("Could not open resample ctx"); - return false; - } else { - Debug(2, "Success opening resampler"); - } -#endif -#endif out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) out_frame->channels = audio_out_ctx->channels; -#endif out_frame->channel_layout = audio_out_ctx->channel_layout; out_frame->sample_rate = audio_out_ctx->sample_rate; @@ -923,7 +874,7 @@ bool VideoStore::setup_resampler() { audio_out_ctx->sample_fmt, 0); converted_in_samples = reinterpret_cast(av_malloc(audioSampleBuffer_size)); - if ( !converted_in_samples ) { + if (!converted_in_samples) { Error("Could not allocate converted in sample pointers"); return false; } else { @@ -931,48 +882,49 @@ bool VideoStore::setup_resampler() { } // Setup the data pointers in the AVFrame - if ( avcodec_fill_audio_frame( + if (avcodec_fill_audio_frame( out_frame, audio_out_ctx->channels, audio_out_ctx->sample_fmt, (const uint8_t *)converted_in_samples, - audioSampleBuffer_size, 0) < 0 ) { + audioSampleBuffer_size, 0) < 0) { Error("Could not allocate converted in sample pointers"); return false; } return true; -#endif } // end bool VideoStore::setup_resampler() -int VideoStore::writePacket(ZMPacket *ipkt) { - if ( ipkt->packet.stream_index == video_in_stream_index ) { +int VideoStore::writePacket(const std::shared_ptr &ipkt) { + if (ipkt->codec_type == AVMEDIA_TYPE_VIDEO) { return writeVideoFramePacket(ipkt); - } else if ( ipkt->packet.stream_index == audio_in_stream_index ) { + } else if (ipkt->codec_type == AVMEDIA_TYPE_AUDIO) { return writeAudioFramePacket(ipkt); } - Error("Unknown stream type in packet (%d) input video stream is (%d) and audio is (%d)", - ipkt->packet.stream_index, video_in_stream_index, ( audio_in_stream ? audio_in_stream_index : -1 ) - ); + Error("Unknown stream type in packet (%d)", ipkt->codec_type); return 0; } -int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { - int ret; +int VideoStore::writeVideoFramePacket(const std::shared_ptr &zm_packet) { frame_count += 1; // if we have to transcode - if ( monitor->GetOptVideoWriter() == Monitor::ENCODE ) { + 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"); - AVFrame *out_frame = zm_packet->get_out_frame(video_out_ctx); - if ( !out_frame ) { + if (!zm_packet->out_frame) { + Debug(3, "Have no out frame. codec is %s sw_pf %d %s hw_pf %d %s %dx%d", + 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), + video_out_ctx->width, video_out_ctx->height + ); + 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 ) { + if (zm_packet->image) { Debug(2, "Have an image, convert it"); //Go straight to out frame swscale.Convert( @@ -980,24 +932,23 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { zm_packet->buffer, zm_packet->codec_imgsize, zm_packet->image->AVPixFormat(), - video_out_ctx->pix_fmt, + chosen_codec_data->sw_pix_fmt, video_out_ctx->width, video_out_ctx->height ); - } else if ( !zm_packet->in_frame ) { + } else if (!zm_packet->in_frame) { Debug(4, "Have no in_frame"); - if ( zm_packet->packet.size ) { + if (zm_packet->packet.size and !zm_packet->decoded) { Debug(4, "Decoding"); - if ( !zm_packet->decode(video_in_ctx) ) { + 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); + Error("Have neither in_frame or image in packet %d!", + zm_packet->image_index); return 0; } // end if has packet or image } else { @@ -1006,39 +957,73 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { } // 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) { + int ret; + 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) - zm_packet->out_frame->pkt_duration = 0; -#endif + frame->pkt_duration = 0; - 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(%d) usecs(%d)", - video_first_pts, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec); - zm_packet->out_frame->pts = 0; + if (!video_first_pts) { + video_first_pts = static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()); + Debug(2, "No video_first_pts, set to (%" PRId64 ") secs(%.2f)", + video_first_pts, + FPSeconds(zm_packet->timestamp.time_since_epoch()).count()); + + frame->pts = 0; } else { - uint64_t useconds = in_pts - video_first_pts; - zm_packet->out_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(%d) usecs(%d) @ %d/%d", - frame_count, zm_packet->out_frame->pts, video_first_pts, useconds, zm_packet->timestamp->tv_sec, zm_packet->timestamp->tv_usec, - video_out_ctx->time_base.num, - video_out_ctx->time_base.den - ); + + Microseconds useconds = std::chrono::duration_cast( + zm_packet->timestamp - SystemTimePoint(Microseconds(video_first_pts))); + frame->pts = av_rescale_q(useconds.count(), AV_TIME_BASE_Q, video_out_ctx->time_base); + Debug(2, + "Setting pts for frame(%d) to (%" PRId64 ") from (zm_packet->timestamp(%" PRIi64 " - first %" PRId64 " us %" PRId64 " ) @ %d/%d", + frame_count, + frame->pts, + static_cast(std::chrono::duration_cast(zm_packet->timestamp.time_since_epoch()).count()), + video_first_pts, + static_cast(std::chrono::duration_cast(useconds).count()), + 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, zm_packet->out_frame, opkt); - if ( ret <= 0 ) { - if ( ret < 0 ) { + int 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; @@ -1046,9 +1031,9 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { 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 ) + 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 ) + 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, @@ -1056,10 +1041,9 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { 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 ) { + 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, @@ -1073,9 +1057,8 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { video_out_stream->time_base.num, video_out_stream->time_base.den ); - } else if ( video_last_pts != AV_NOPTS_VALUE ) { - duration = - av_rescale_q( + } 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); @@ -1085,8 +1068,10 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { 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); + 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; @@ -1094,7 +1079,6 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { //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"); @@ -1105,8 +1089,8 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { opkt.flags = ipkt->flags; opkt.duration = ipkt->duration; - if ( ipkt->dts != AV_NOPTS_VALUE ) { - if ( !video_first_dts ) { + 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; } @@ -1115,36 +1099,35 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { 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 ) { + 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(ZMPacket *zm_packet) { - - AVPacket *ipkt = &zm_packet->packet; - int ret; - - if ( !audio_out_stream ) { +int VideoStore::writeAudioFramePacket(const std::shared_ptr &zm_packet) { + 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 } + + AVPacket *ipkt = &zm_packet->packet; + int ret; ZM_DUMP_STREAM_PACKET(audio_in_stream, (*ipkt), "input packet"); - if ( !audio_first_dts ) { + if (!audio_first_dts) { audio_first_dts = ipkt->dts; audio_next_pts = audio_out_ctx->frame_size; } @@ -1152,10 +1135,10 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { 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 - if ( audio_out_codec ) { + if (audio_out_codec) { // I wonder if we can get multiple frames per packet? Probably ret = zm_send_packet_receive_frame(audio_in_ctx, in_frame, *ipkt); - if ( ret < 0 ) { + if (ret < 0) { Debug(3, "failed to receive frame code: %d", ret); return 0; } @@ -1163,15 +1146,15 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { AVFrame *input_frame = in_frame; - while ( zm_resample_audio(resample_ctx, input_frame, out_frame) ) { + while (zm_resample_audio(resample_ctx, input_frame, out_frame)) { //out_frame->pkt_duration = in_frame->pkt_duration; // resampling doesn't alter duration - if ( zm_add_samples_to_fifo(fifo, out_frame) <= 0 ) + if (zm_add_samples_to_fifo(fifo, out_frame) <= 0) break; // We put the samples into the fifo so we are basically resetting the frame out_frame->nb_samples = audio_out_ctx->frame_size; - if ( zm_get_samples_from_fifo(fifo, out_frame) <= 0 ) + if (zm_get_samples_from_fifo(fifo, out_frame) <= 0) break; out_frame->pts = audio_next_pts; @@ -1180,7 +1163,7 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { zm_dump_frame(out_frame, "Out frame after resample"); av_init_packet(&opkt); - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, opkt) <= 0 ) + if (zm_send_frame_receive_packet(audio_out_ctx, out_frame, opkt) <= 0) break; // Scale the PTS of the outgoing packet to be the correct time base @@ -1191,12 +1174,11 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { write_packet(&opkt, audio_out_stream); zm_av_packet_unref(&opkt); - if ( zm_resample_get_delay(resample_ctx, out_frame->sample_rate) < out_frame->nb_samples) + if (zm_resample_get_delay(resample_ctx, out_frame->sample_rate) < out_frame->nb_samples) break; // This will send a null frame, emptying out the resample buffer input_frame = nullptr; - } // end while there is data in the resampler - + } // end while there is data in the resampler } else { av_init_packet(&opkt); opkt.data = ipkt->data; @@ -1218,42 +1200,19 @@ int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { return 0; } // end int VideoStore::writeAudioFramePacket(AVPacket *ipkt) -int VideoStore::write_packets(PacketQueue &queue) { - // 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; - - while ( ( queued_packet = queue.popPacket() ) ) { - AVPacket *avp = queued_packet->av_packet(); - - 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, queue.size() ); - int ret = this->writePacket( queued_packet ); - 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 ); - return packet_count; -} // end int VideoStore::write_packets( PacketQueue &queue ) { - int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { pkt->pos = -1; pkt->stream_index = stream->index; - if ( pkt->dts == AV_NOPTS_VALUE ) { + if (pkt->dts == AV_NOPTS_VALUE) { Debug(1, "undef dts, fixing by setting to stream cur_dts %" PRId64, stream->cur_dts); pkt->dts = stream->cur_dts; - } else if ( pkt->dts < stream->cur_dts ) { + } 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 ) { + if (pkt->dts > pkt->pts) { Debug(1, "pkt.dts(%" PRId64 ") must be <= pkt.pts(%" PRId64 ")." "Decompression must happen before presentation.", @@ -1267,9 +1226,8 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { 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()); + if (ret != 0) { + Error("Error writing packet: %s", av_make_error_string(ret).c_str()); } else { Debug(4, "Success writing packet"); } diff --git a/src/zm_videostore.h b/src/zm_videostore.h index d350db65b..c73ed19db 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -6,18 +6,15 @@ #include "zm_ffmpeg.h" #include "zm_swscale.h" -extern "C" { -#ifdef HAVE_LIBSWRESAMPLE - #include "libswresample/swresample.h" -#else - #ifdef HAVE_LIBAVRESAMPLE - #include "libavresample/avresample.h" - #endif -#endif -#include "libavutil/audio_fifo.h" -} +#include -#if HAVE_LIBAVCODEC +extern "C" { +#include +#include +#if HAVE_LIBAVUTIL_HWCONTEXT_H +#include +#endif +} class Monitor; class ZMPacket; @@ -30,93 +27,98 @@ class VideoStore { const AVCodecID codec_id; const char *codec_codec; const char *codec_name; - const enum AVPixelFormat pix_fmt; + const enum AVPixelFormat sw_pix_fmt; + const enum AVPixelFormat hw_pix_fmt; +#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0) + const AVHWDeviceType hwdevice_type; +#endif }; static struct CodecData codec_data[]; + CodecData *chosen_codec_data; - Monitor *monitor; - AVOutputFormat *out_format; - AVFormatContext *oc; - AVStream *video_out_stream; - AVStream *audio_out_stream; - int video_in_stream_index; - int audio_in_stream_index; + Monitor *monitor; + AVOutputFormat *out_format; + AVFormatContext *oc; + AVStream *video_out_stream; + AVStream *audio_out_stream; - AVCodec *video_out_codec; - AVCodecContext *video_in_ctx; - AVCodecContext *video_out_ctx; + AVCodecContext *video_in_ctx; + AVCodecContext *video_out_ctx; - AVStream *video_in_stream; - AVStream *audio_in_stream; + 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; + 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; + SWScale swscale; + unsigned int packets_written; + unsigned int frame_count; -#ifdef HAVE_LIBSWRESAMPLE - SwrContext *resample_ctx; -#else -#ifdef HAVE_LIBAVRESAMPLE - 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_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; + AVBufferRef *hw_device_ctx; - // 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; + SwrContext *resample_ctx; + AVAudioFifo *fifo; + uint8_t *converted_in_samples; - int max_stream_index; + const char *filename; + const char *format; - bool setup_resampler(); - int write_packet(AVPacket *pkt, AVStream *stream); + // These are for in + int64_t video_first_pts; /* starting pts of first in frame/packet */ + 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; -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(); + // 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; - 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(); + int max_stream_index; + + bool setup_resampler(); + int write_packet(AVPacket *pkt, AVStream *stream); + + 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(const std::shared_ptr &pkt); + int writeAudioFramePacket(const std::shared_ptr &pkt); + int writePacket(const std::shared_ptr &pkt); + int write_packets(PacketQueue &queue); + void flush_codecs(); + const char *get_codec() { + if (chosen_codec_data) + return chosen_codec_data->codec_codec; + if (video_out_stream) + return avcodec_get_name(video_out_stream->codecpar->codec_id); + return ""; + } }; -#endif //havelibav -#endif //zm_videostore_h +#endif // ZM_VIDEOSTORE_H diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 50c287ec8..f0c09ec78 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -20,12 +20,10 @@ #include "zm_zone.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, +void Zone::Setup( ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, @@ -34,7 +32,7 @@ void Zone::Setup( int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, - const Coord &p_filter_box, + const Vector2 &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, @@ -44,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; @@ -67,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; @@ -107,36 +94,47 @@ void Zone::Setup( } } - if ( config.record_diag_images ) { - if ( config.record_diag_images_fifo ) { - snprintf(diag_path, sizeof(diag_path), - "%s/diagpipe-%d-poly.jpg", + 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); - FifoStream::fifo_create_if_missing(diag_path); + Fifo::fifo_create_if_missing(diag_path.c_str()); } else { - snprintf(diag_path, sizeof(diag_path), "%s/diag-%d-poly.jpg", + diag_path = stringtf("%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); } + pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); - } else { - diag_path[0] = 0; } } // end Zone::Setup Zone::~Zone() { - delete[] label; - if ( image ) + if (image) delete image; delete pg_image; delete[] ranges; } void Zone::RecordStats(const Event *event) { - static char sql[ZM_SQL_MED_BUFSIZ]; - 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 + std::string sql = stringtf( + "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_.Lo().x_, + stats.alarm_box_.Lo().y_, + stats.alarm_box_.Hi().x_, + stats.alarm_box_.Hi().y_, + stats.score_ ); zmDbDo(sql); } // end void Zone::RecordStats( const Event *event ) @@ -151,7 +149,7 @@ 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) { @@ -197,18 +195,18 @@ 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; } - if ( image ) + if (image) delete image; // Get the difference image Image *diff_image = image = new Image(*delta_image); int diff_width = diff_image->Width(); - uint8_t* diff_buff = (uint8_t*)diff_image->Buffer(); + uint8_t* diff_buff = diff_image->Buffer(); uint8_t* pdiff; unsigned int pixel_diff_count = 0; @@ -221,39 +219,40 @@ bool Zone::CheckAlarms(const Image *delta_image) { int alarm_mid_x = -1; int alarm_mid_y = -1; - unsigned int lo_y = polygon.LoY(); - unsigned int lo_x = polygon.LoX(); - unsigned int hi_x = polygon.HiX(); - unsigned int hi_y = polygon.HiY(); + unsigned int lo_x = polygon.Extent().Lo().x_; + unsigned int lo_y = polygon.Extent().Lo().y_; + unsigned int hi_x = polygon.Extent().Hi().x_; + unsigned int hi_y = polygon.Extent().Hi().y_; - 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 ) + if (config.record_diag_images) { diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo); - - if ( pixel_diff_count && alarm_pixels ) - pixel_diff = pixel_diff_count/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); - - if ( config.record_diag_images_fifo ) { - FifoDebug(5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}", - id, alarm_pixels, pixel_diff); } - if ( alarm_pixels ) { - if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_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", + stats.alarm_pixels_, min_alarm_pixels, max_alarm_pixels, stats.pixel_diff_); + + if (config.record_diag_images_fifo) { + FifoDebug(5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}", + id, stats.alarm_pixels_, stats.pixel_diff_); + } + + 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; @@ -263,95 +262,99 @@ bool Zone::CheckAlarms(const Image *delta_image) { return false; } - score = (100*alarm_pixels)/(max_alarm_pixels?max_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 ) { - int bx = filter_box.X(); - int by = filter_box.Y(); + 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); + pdiff = diff_image->Buffer(lo_x, y); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { - if ( *pdiff == kWhite ) { + 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 ) { + 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 ) + if (config.record_diag_images) { diff_image->WriteJpeg(diag_path, 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"); // ICON FIXME Would like to get rid of this memset memset(blob_stats, 0, sizeof(BlobStats)*256); @@ -359,17 +362,16 @@ bool Zone::CheckAlarms(const Image *delta_image) { 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 == kWhite ) { + pdiff = 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 > 0) && ( (x-1) >= lo_x )) ? *(pdiff-1) : 0; - last_y = 0; if ( y > 0 ) { if ( (y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x ) { @@ -377,20 +379,20 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - 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; @@ -406,7 +408,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; @@ -415,18 +417,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", @@ -439,17 +441,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; @@ -463,50 +465,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 = (kWhite-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 ) { + 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; @@ -516,21 +518,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; @@ -541,41 +543,43 @@ bool Zone::CheckAlarms(const Image *delta_image) { } } - if ( config.record_diag_images ) + if (config.record_diag_images) { diff_image->WriteJpeg(diag_path, 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 ( uint32 i = 1; i < kWhite; 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 ) { + 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; @@ -585,69 +589,73 @@ 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 ) + if (config.record_diag_images) { diff_image->WriteJpeg(diag_path, 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); - - if ( config.record_diag_images_fifo ) { - FifoDebug(5, "{\"zone\":%d,\"type\":\"FBLB\",\"pixels\":%d,\"blobs\":%d}", - id, alarm_blob_pixels, alarm_blobs); } - if ( alarm_blobs ) { - if ( min_blobs && (alarm_blobs < min_blobs) ) { + Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", + stats.alarm_blob_pixels_, stats.alarm_blobs_, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs); + + if (config.record_diag_images_fifo) { + FifoDebug(5, "{\"zone\":%d,\"type\":\"FBLB\",\"pixels\":%d,\"blobs\":%d}", + id, stats.alarm_blob_pixels_, stats.alarm_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; - else - score = (100*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); - alarm_lo_x = polygon.HiX()+1; - alarm_hi_x = polygon.LoX()-1; - alarm_lo_y = polygon.HiY()+1; - alarm_hi_y = polygon.LoY()-1; + if (max_blob_pixels != 0) + stats.score_ = (100*stats.alarm_blob_pixels_)/max_blob_pixels; + else + stats.score_ = (100*stats.alarm_blob_pixels_)/polygon.Area(); - for ( uint32 i = 1; i < kWhite; i++ ) { + 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.Extent().Hi().x_ + 1; + alarm_hi_x = polygon.Extent().Lo().x_ - 1; + alarm_lo_y = polygon.Extent().Hi().y_ + 1; + alarm_hi_y = polygon.Extent().Lo().y_ - 1; + + 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 { @@ -656,10 +664,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 { @@ -668,39 +676,39 @@ 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(Vector2(alarm_lo_x, alarm_lo_y), Vector2(alarm_hi_x, alarm_hi_y)); //if ( monitor->followMotion() ) if ( true ) { - alarm_centre = Coord(alarm_mid_x, alarm_mid_y); + stats.alarm_centre_ = Vector2(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 ) { + if (lo_gap > 0) { + if (lo_gap == 1) { *pdiff++ = kBlack; } else { memset(pdiff, kBlack, lo_gap); @@ -709,23 +717,23 @@ bool Zone::CheckAlarms(const Image *delta_image) { } const uint8_t* ppoly = pg_image->Buffer(lo_x2, y); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) { - if ( !*ppoly ) { + 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 ) { + if (hi_gap > 0) { + if (hi_gap == 1) { *pdiff = kBlack; } else { 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()); @@ -737,47 +745,46 @@ bool Zone::CheckAlarms(const Image *delta_image) { } // 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) { - char *str = (char *)poly_string; - char *ws; - char *cp; - int n_coords = 0; int max_n_coords = strlen(str)/4; - Coord *coords = new Coord[max_n_coords]; - while ( *str != '\0' ) { - cp = strchr(str, ','); - if ( !cp ) { + + std::vector vertices; + vertices.reserve(max_n_coords); + + while (*str != '\0') { + char *cp = strchr(str, ','); + if (!cp) { Error("Bogus coordinate %s found in polygon string", str); break; - } + } + int x = atoi(str); - int y = atoi(cp+1); + int y = atoi(cp + 1); Debug(3, "Got coordinate %d,%d from polygon string", x, y); - coords[n_coords++] = Coord(x, y); + vertices.emplace_back(x, y); - ws = strchr(cp+2, ' '); - if ( ws ) - str = ws+1; - else + char *ws = strchr(cp + 2, ' '); + if (ws) { + str = ws + 1; + } else { break; - } // end while ! end of string - - 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; - return n_coords ? true : false; + if (vertices.size() > 2) { + Debug(3, "Successfully parsed polygon string %s", str); + polygon = Polygon(vertices); + } else { + Error("Not enough coordinates to form a polygon!"); + } + + return !vertices.empty(); } // end bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) { @@ -817,43 +824,35 @@ bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, P 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]; - MYSQL_RES *result; +std::vector Zone::Load(Monitor *monitor) { + std::vector zones; - { // scope for lock - std::lock_guard lck(db_mutex); - 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)); - 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()); - result = mysql_store_result(&dbconn); - } + MYSQL_RES *result = zmDbFetch(sql); if (!result) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - return 0; + return {}; } - 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++; @@ -876,18 +875,24 @@ 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() ) { + if (polygon.Extent().Lo().x_ < 0 || polygon.Extent().Hi().x_ > static_cast(monitor->Width()) + || polygon.Extent().Lo().y_ < 0 || polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { 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()); - 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()); + Id, + Name, + monitor->Name(), + polygon.Extent().Lo().x_, + polygon.Extent().Lo().y_, + polygon.Extent().Hi().x_, + polygon.Extent().Hi().y_); + + polygon.Clip(Box( + {0, 0}, + {static_cast(monitor->Width()), static_cast(monitor->Height())} + )); } if ( false && !strcmp( Units, "Percent" ) ) { @@ -899,29 +904,29 @@ 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[i] = new Zone( - monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, - (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, - MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), + zones.emplace_back( + monitor, Id, Name, Type, polygon, AlarmRGB, + CheckMethod, MinPixelThreshold, MaxPixelThreshold, + MinAlarmPixels, MaxAlarmPixels, Vector2(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), " Label : %s\n", label.c_str() ); sprintf(output+strlen(output), " Type: %d - %s\n", type, type==ACTIVE?"Active":( type==INCLUSIVE?"Inclusive":( @@ -930,9 +935,9 @@ bool Zone::DumpSettings(char *output, bool /*verbose*/) { type==INACTIVE?"Inactive":( type==PRIVACY?"Privacy":"Unknown" )))))); - sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); - for ( int i = 0; i < polygon.getNumCoords(); i++ ) { - sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); + sprintf( output+strlen(output), " Shape : %zu points\n", polygon.GetVertices().size() ); + for (size_t i = 0; i < polygon.GetVertices().size(); i++) { + sprintf(output + strlen(output), " %zu: %d,%d\n", i, polygon.GetVertices()[i].x_, polygon.GetVertices()[i].y_); } sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, @@ -944,7 +949,7 @@ bool Zone::DumpSettings(char *output, bool /*verbose*/) { sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); sprintf( output+strlen(output), " Max Alarm Pixels : %d\n", max_alarm_pixels ); - sprintf( output+strlen(output), " Filter Box : %d,%d\n", filter_box.X(), filter_box.Y() ); + sprintf(output+strlen(output), " Filter Box : %d,%d\n", filter_box.x_, filter_box.y_ ); sprintf( output+strlen(output), " Min Filter Pixels : %d\n", min_filter_pixels ); sprintf( output+strlen(output), " Max Filter Pixels : %d\n", max_filter_pixels ); sprintf( output+strlen(output), " Min Blob Pixels : %d\n", min_blob_pixels ); @@ -968,14 +973,14 @@ void Zone::std_alarmedpixels( if ( max_pixel_threshold ) calc_max_pixel_threshold = max_pixel_threshold; - lo_y = polygon.LoY(); - hi_y = polygon.HiY(); + lo_y = polygon.Extent().Lo().y_; + hi_y = polygon.Extent().Hi().y_; for ( unsigned int y = lo_y; y <= hi_y; y++ ) { unsigned int lo_x = ranges[y].lo_x; unsigned int hi_x = ranges[y].hi_x; Debug(7, "Checking line %d from %d -> %d", y, lo_x, hi_x); - uint8_t *pdiff = (uint8_t*)pdiff_image->Buffer(lo_x, y); + uint8_t *pdiff = pdiff_image->Buffer(lo_x, y); const uint8_t *ppoly = ppoly_image->Buffer(lo_x, y); for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) { @@ -994,3 +999,41 @@ void Zone::std_alarmedpixels( *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 72116b022..741b61ae2 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -21,11 +21,16 @@ #define ZM_ZONE_H #include "zm_box.h" -#include "zm_coord.h" #include "zm_define.h" #include "zm_config.h" #include "zm_poly.h" #include "zm_rgb.h" +#include "zm_zone_stats.h" +#include "zm_vector2.h" + +#include +#include +#include class Event; class Image; @@ -35,147 +40,196 @@ 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; - }; - typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; + 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; -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; + std::string label; + ZoneType type; + Polygon polygon; + Rgb alarm_rgb; + CheckMethod check_method; - int id; - char *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; + Vector2 filter_box; + int min_filter_pixels; + int max_filter_pixels; - Coord filter_box; - int min_filter_pixels; - int max_filter_pixels; + BlobStats blob_stats[256]; + 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; + ZoneStats stats; + Image *pg_image; + Range *ranges; + Image *image; - // 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; + int overload_count; + int extend_alarm_count; + std::string diag_path; - int overload_count; - int extend_alarm_count; - char diag_path[PATH_MAX]; + 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 Vector2 &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); -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, 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 ) - { - Setup( p_monitor, p_id, p_label, p_type, p_polygon, kRGBBlack, (Zone::CheckMethod)0, 0, 0, 0, 0, Coord( 0, 0 ), 0, 0, 0, 0, 0, 0, 0, 0 ); - } + void std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum); -public: - ~Zone(); + 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 Vector2 &p_filter_box = Vector2(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 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 ); } + 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, Vector2(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, Vector2(0, 0), 0, 0, 0, 0, 0, 0, 0, 0 ); + } - 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(const Zone &z); + ~Zone(); - 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); + 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 Vector2 GetAlarmCentre() const { return stats.alarm_centre_; } + inline unsigned int Score() const { return stats.score_; } - inline const Image *getPgImage() const { return( pg_image ); } - inline const Range *getRanges() const { return( ranges ); } + 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..6243f53b6 --- /dev/null +++ b/src/zm_zone_stats.h @@ -0,0 +1,89 @@ +// +// 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_logger.h" +#include "zm_vector2.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), + 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_ = {}; + 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_.Lo().x_, + alarm_box_.Lo().y_, + alarm_box_.Hi().x_, + alarm_box_.Hi().y_, + 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_; + Vector2 alarm_centre_; + unsigned int score_; +}; + +#endif // ZM_ZONE_STATS_H diff --git a/src/zmbenchmark.cpp b/src/zmbenchmark.cpp new file mode 100644 index 000000000..5e6b93e42 --- /dev/null +++ b/src/zmbenchmark.cpp @@ -0,0 +1,326 @@ +// +// ZoneMinder Benchmark, $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 +#include +#include +#include +#include +#include + +#include "zm_config.h" +#include "zm_image.h" +#include "zm_monitor.h" +#include "zm_time.h" +#include "zm_utils.h" +#include "zm_zone.h" + +static std::mt19937 mt_rand(111); + +// +// This allows you to feed in a set of columns and timing rows, and print it +// out in a nice-looking table. +// +class TimingsTable { + public: + explicit TimingsTable(std::vector in_columns) : columns_(std::move(in_columns)) {} + + // + // Add a row to the end of the table. + // + // Args: + // label: The name of the row (printed in the first column). + // timings: The values for all the other columns in this row. + void AddRow(const std::string &label, const std::vector &timings) { + assert(timings.size() == columns_.size()); + Row row; + row.label = label; + row.timings = timings; + rows_.push_back(row); + } + + // + // Print out the table. + // + // Args: + // columnPad: # characters between table columns + // + void Print(const int column_pad = 5) { + // Figure out column widths. + std::vector widths(columns_.size() + 1); + + // The first width is the max of the row labels. + auto result = std::max_element(rows_.begin(), + rows_.end(), + [](const Row &a, const Row &b) -> bool { + return a.label.length() < b.label.length(); + }); + widths[0] = result->label.length() + column_pad; + + // Calculate the rest of the column widths. + for (size_t i = 0 ; i < columns_.size() ; i++) + widths[i + 1] = columns_[i].length() + column_pad; + + auto PrintColStr = [&](size_t icol, const std::string &str) { + printf("%s", str.c_str()); + PrintPadding(widths[icol] - str.length()); + }; + + // Print the header. + PrintColStr(0, ""); + for (size_t i = 0 ; i < columns_.size() ; i++) { + PrintColStr(i + 1, columns_[i]); + } + printf("\n"); + + // Print the timings rows. + for (const Row &row : rows_) { + PrintColStr(0, row.label); + + for (size_t i = 0 ; i < row.timings.size() ; i++) { + std::string num = stringtf("%.2f", std::chrono::duration_cast(row.timings[i]).count()); + PrintColStr(i + 1, num); + } + + printf("\n"); + } + } + + private: + static void PrintPadding(size_t count) { + std::string str(count, ' '); + printf("%s", str.c_str()); + } + + struct Row { + std::string label; + std::vector timings; + }; + + std::vector columns_; + std::vector rows_; +}; + +// +// Generate a greyscale image that simulates a delta that can be fed to +// Zone::CheckAlarms. This first creates a black image, and then it fills +// a box of a certain size inside the image with random data. This is to simulate +// a typical scene where most of the scene doesn't change except a specific region. +// +// Args: +// changeBoxPercent: 0-100 value telling how large the box with random data should be. +// Set to 0 to leave the whole thing black. +// width: The width of the new image. +// height: The height of the new image. +// +// Return: +// An image with all pixels initialized to values in the [minVal,maxVal] range. +// +std::shared_ptr GenerateRandomImage( + const int change_box_percent, + const int width = 3840, + const int height = 2160) { + // Create the image. + Image *image = new Image(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); + + // Set it to black initially. + memset(image->Buffer(0, 0), 0, (size_t) image->LineSize() * (size_t) image->Height()); + + // Now randomize the pixels inside a box. + const int box_width = (width * change_box_percent) / 100; + const int box_height = (height * change_box_percent) / 100; + const int box_x = (int) ((uint64_t) mt_rand() * (width - box_width) / RAND_MAX); + const int box_y = (int) ((uint64_t) mt_rand() * (height - box_height) / RAND_MAX); + + for (int y = 0 ; y < box_height ; y++) { + uint8_t *row = image->Buffer(box_x, box_y + y); + for (int x = 0 ; x < box_width ; x++) { + row[x] = (uint8_t) mt_rand(); + } + } + + return std::shared_ptr(image); +} + +// +// This is used to help rig up Monitor benchmarks. +// +class TestMonitor : public Monitor { + public: + TestMonitor(int width, int height) : cur_zone_id(111) { + this->width = width; + this->height = height; + + // Create a dummy ref_image. + std::shared_ptr tempImage = GenerateRandomImage(0, width, height); + ref_image = *tempImage; + + shared_data = &temp_shared_data; + } + + // + // Add a new zone to this monitor. + // + // Args: + // checkMethod: This controls how this zone will actually do motion detection. + // + // p_filter_box: The size of the filter to use. + // + void AddZone(Zone::CheckMethod checkMethod, const Vector2 &p_filter_box = Vector2(5, 5)) { + const int p_min_pixel_threshold = 50; + const int p_max_pixel_threshold = 255; + const int p_min_alarm_pixels = 1000; + const int p_max_alarm_pixels = 10000000; + + const int zone_id = cur_zone_id++; + const std::string zone_label = std::string("zone_") + std::to_string(zone_id); + const Zone::ZoneType zone_type = Zone::ZoneType::ACTIVE; + const Polygon poly({Vector2(0, 0), + Vector2(width - 1, 0), + Vector2(width - 1, height - 1), + Vector2(0, height - 1)}); + + Zone zone(this, + zone_id, + zone_label.c_str(), + zone_type, + poly, + kRGBGreen, + Zone::CheckMethod::FILTERED_PIXELS, + p_min_pixel_threshold, + p_max_pixel_threshold, + p_min_alarm_pixels, + p_max_alarm_pixels, + p_filter_box); + zones.push_back(zone); + } + + void SetRefImage(const Image *image) { + ref_image = *image; + } + + private: + SharedData temp_shared_data; + int cur_zone_id; +}; + +// +// Run zone benchmarks on the given image. +// +// Args: +// label: A label to be printed before the output. +// +// image: The image to run the tests on. +// +// p_filter_box: The size of the filter to use for alarm detection. +// +// Return: +// The average time taken for each DetectMotion call. +// +Microseconds RunDetectMotionBenchmark(const std::string &label, + const std::shared_ptr& image, + const Vector2 &p_filter_box) { + // Create a monitor to use for the benchmark. Give it 1 zone that uses + // a 5x5 filter. + TestMonitor testMonitor(image->Width(), image->Height()); + testMonitor.AddZone(Zone::CheckMethod::FILTERED_PIXELS, p_filter_box); + + // Generate a black image to use as the reference image. + std::shared_ptr blackImage = GenerateRandomImage( + 0, image->Width(), image->Height()); + testMonitor.SetRefImage(blackImage.get()); + + Microseconds totalTimeTaken(0); + + // Run a series of passes over DetectMotion. + const int numPasses = 10; + for (int i = 0 ; i < numPasses ; i++) { + printf("\r%s - pass %2d / %2d ", label.c_str(), i + 1, numPasses); + fflush(stdout); + + TimeSegmentAdder adder(totalTimeTaken); + + Event::StringSet zoneSet; + testMonitor.DetectMotion(*image, zoneSet); + } + printf("\n"); + + return totalTimeTaken / numPasses; +} + +// +// This runs a set of Monitor::DetectMotion benchmarks, one for each of the +// "delta box percents" that are passed in. This adds one row to the +// TimingsTable specified. +// +// Args: +// table: The table to add timings into. +// +// deltaBoxPercents: Each of these defines a box size in the delta images +// passed to DetectMotion (larger boxes make it slower, sometimes significantly so). +// +// p_filter_box: Defines the filter size used in DetectMotion. +// +void RunDetectMotionBenchmarks( + TimingsTable &table, + const std::vector &delta_box_percents, + const Vector2 &p_filter_box) { + std::vector timings; + + for (int percent : delta_box_percents) { + Microseconds timing = RunDetectMotionBenchmark( + std::string("DetectMotion: ") + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + + " box, " + std::to_string(percent) + "% delta", + GenerateRandomImage(percent), + p_filter_box); + timings.push_back(timing); + } + + table.AddRow( + std::to_string(p_filter_box.x_) + "x" + std::to_string(p_filter_box.y_) + " filter", + timings); +} + +int main(int argc, char *argv[]) { + // Init global stuff that we need. + config.font_file_location = "../fonts/default.zmfnt"; + config.event_close_mode = "time"; + config.cpu_extensions = true; + + // Detect SSE version. + HwCapsDetect(); + + // Setup the column titles for the TimingsTable we'll generate. + // Each column represents how large the box in the image is with delta pixels. + // Each row represents a different filter size. + const std::vector percents = {0, 10, 50, 100}; + std::vector columns(percents.size()); + std::transform(percents.begin(), percents.end(), columns.begin(), + [](const int percent) { return std::to_string(percent) + "% delta (ms)"; }); + TimingsTable table(columns); + + std::vector filterSizes = {Vector2(3, 3), Vector2(5, 5), Vector2(13, 13)}; + for (const auto filterSize : filterSizes) { + RunDetectMotionBenchmarks(table, percents, filterSize); + } + + table.Print(); + return 0; +} + diff --git a/src/zmc.cpp b/src/zmc.cpp index fc3fe78a3..669fa7af3 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -54,10 +54,10 @@ possible, this should run at more or less constant speed. */ #include "zm.h" -#include "zm_analysis_thread.h" #include "zm_camera.h" #include "zm_db.h" #include "zm_define.h" +#include "zm_fifo.h" #include "zm_monitor.h" #include "zm_rtsp_server_thread.h" #include "zm_signal.h" @@ -65,19 +65,18 @@ possible, this should run at more or less constant speed. #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); @@ -189,14 +188,14 @@ int main(int argc, char *argv[]) { zmLoadDBConfig(); logInit(logId); - hwcaps_detect(); + HwCapsDetect(); std::vector> monitors; -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 if ( device[0] ) { monitors = Monitor::LoadLocalMonitors(device, Monitor::CAPTURE); } else -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 if ( host[0] ) { if ( !port ) port = "80"; @@ -214,7 +213,7 @@ int main(int argc, char *argv[]) { Error("No monitors found"); exit(-1); } else { - Debug(2, "%d monitors loaded", monitors.size()); + Debug(2, "%zu monitors loaded", monitors.size()); } Info("Starting Capture version %s", ZM_VERSION); @@ -234,7 +233,6 @@ int main(int argc, char *argv[]) { while (!zm_terminate) { result = 0; - static char sql[ZM_SQL_SML_BUFSIZ]; for (const std::shared_ptr &monitor : monitors) { monitor->LoadCamera(); @@ -242,74 +240,51 @@ int main(int argc, char *argv[]) { if (!monitor->connect()) { Warning("Couldn't connect to monitor %d", monitor->Id()); } - time_t now = (time_t)time(nullptr); - monitor->setStartupTime(now); + SystemTimePoint now = std::chrono::system_clock::now(); + monitor->SetStartupTime(now); + monitor->SetHeartbeatTime(now); - snprintf(sql, sizeof(sql), + std::string sql = stringtf( "INSERT INTO Monitor_Status (MonitorId,Status,CaptureFPS,AnalysisFPS)" - " VALUES (%d, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", + " VALUES (%u, 'Running',0,0) ON DUPLICATE KEY UPDATE Status='Running',CaptureFPS=0,AnalysisFPS=0", monitor->Id()); zmDbDo(sql); + Seconds sleep_time = Seconds(0); while (monitor->PrimeCapture() <= 0) { if (prime_capture_log_count % 60) { - Error("Failed to prime capture of initial monitor"); + 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; - sleep(1); - } - if (zm_terminate) break; - snprintf(sql, sizeof(sql), - "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Connected') ON DUPLICATE KEY UPDATE Status='Connected'", - monitor->Id()); + prime_capture_log_count++; + if (zm_terminate) { + break; + } + if (sleep_time < Seconds(60)) { + sleep_time++; + } + + std::this_thread::sleep_for(sleep_time); + } + if (zm_terminate) { + break; + } + + sql = stringtf( + "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; -#if HAVE_RTSP_SERVER - RTSPServerThread ** rtsp_server_threads = nullptr; - if (config.min_rtsp_port and monitors[0]->RTSPServer()) { - rtsp_server_threads = new RTSPServerThread *[monitors.size()]; - Debug(1, "Starting RTSP server because min_rtsp_port is set"); - } else { - Debug(1, "Not starting RTSP server because min_rtsp_port not set"); - } -#endif - - std::vector> analysis_threads = std::vector>(); - - 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()]; - - 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(); - Debug(2, "capture delay(%u mSecs 1000/capture_fps) alarm delay(%u)", - capture_delays[i], alarm_capture_delays[i]); - - Monitor::Function function = monitors[0]->GetFunction(); - if (function != Monitor::MONITOR) { - Debug(1, "Starting an analysis thread for monitor (%d)", monitors[i]->Id()); - analysis_threads.emplace_back(ZM::make_unique(monitors[i])); - } -#if HAVE_RTSP_SERVER - if (rtsp_server_threads) { - rtsp_server_threads[i] = new RTSPServerThread(monitors[i]); - rtsp_server_threads[i]->addStream(monitors[i]->GetVideoStream(), monitors[i]->GetAudioStream()); - rtsp_server_threads[i]->start(); - } -#endif + if (zm_terminate){ + break; } - struct timeval now; - struct DeltaTimeval delta_time; - int sleep_time = 0; + std::vector last_capture_times = std::vector(monitors.size()); + Microseconds sleep_time = Microseconds(0); while (!zm_terminate) { //sigprocmask(SIG_BLOCK, &block_set, 0); @@ -317,47 +292,50 @@ int main(int argc, char *argv[]) { monitors[i]->CheckAction(); if (monitors[i]->PreCapture() < 0) { - Error("Failed to pre-capture monitor %d %d (%d/" SZFMTD ")", - monitors[i]->Id(), monitors[i]->Name(), i+1, monitors.size()); + Error("Failed to pre-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); result = -1; break; } if (monitors[i]->Capture() < 0) { - Error("Failed to capture image from monitor %d %s (%d/" SZFMTD ")", - monitors[i]->Id(), monitors[i]->Name(), i+1, monitors.size()); + 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 (%d/" SZFMTD ")", - monitors[i]->Id(), monitors[i]->Name(), i+1, monitors.size()); + Error("Failed to post-capture monitor %d %s (%zu/%zu)", + monitors[i]->Id(), monitors[i]->Name(), i + 1, monitors.size()); result = -1; break; } + monitors[i]->UpdateFPS(); - gettimeofday(&now, nullptr); // 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 && 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); + Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay() + : monitors[i]->GetCaptureDelay(); + if (delay != Seconds(0)) { + SystemTimePoint now = std::chrono::system_clock::now(); + if (last_capture_times[i].time_since_epoch() != Seconds(0)) { + Microseconds delta_time = std::chrono::duration_cast(now - last_capture_times[i]); - // 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:%d.%d last:%d.%d delta %d delay: %d", - sleep_time, - now.tv_sec, now.tv_usec, - last_capture_times[i].tv_sec, 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; + // You have to add back in the previous sleep time + sleep_time = delay - (delta_time - sleep_time); + Debug(4, + "Sleep time is %" PRIi64 " from now: %.2f s last: %.2f s delta % " PRIi64 " us delay: %" PRIi64 " us", + static_cast(Microseconds(sleep_time).count()), + FPSeconds(now.time_since_epoch()).count(), + FPSeconds(last_capture_times[i].time_since_epoch()).count(), + static_cast(delta_time.count()), + static_cast(Microseconds(delay).count())); + + if (sleep_time > Seconds(0)) { + std::this_thread::sleep_for(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) { @@ -366,45 +344,11 @@ int main(int argc, char *argv[]) { } } // end while ! zm_terminate and connected - for (std::unique_ptr &analysis_thread: analysis_threads) - analysis_thread->Stop(); - - for (size_t i = 0; i < monitors.size(); i++) { -#if HAVE_RTSP_SERVER - if (rtsp_server_threads) { - rtsp_server_threads[i]->stop(); - } -#endif - - monitors[i]->Close(); - -#if HAVE_RTSP_SERVER - if (rtsp_server_threads) { - rtsp_server_threads[i]->join(); - delete rtsp_server_threads[i]; - rtsp_server_threads[i] = nullptr; - } -#endif + for (std::shared_ptr & monitor : monitors) { + monitor->Close(); + monitor->disconnect(); } - // Killoff the analysis threads. Don't need them spinning while we try to reconnect - analysis_threads.clear(); - -#if HAVE_RTSP_SERVER - if (rtsp_server_threads) { - delete[] rtsp_server_threads; - rtsp_server_threads = nullptr; - } -#endif - delete [] alarm_capture_delays; - delete [] capture_delays; - delete [] last_capture_times; - - if (result < 0 and !zm_terminate) { - // Failure, try reconnecting - Debug(1, "Sleeping for 5"); - sleep(5); - } if (zm_reload) { for (std::shared_ptr &monitor : monitors) { monitor->Reload(); @@ -415,12 +359,9 @@ int main(int argc, char *argv[]) { } // 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), - "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%d, 'Connected') ON DUPLICATE KEY UPDATE Status='NotRunning'", + std::string sql = stringtf( + "INSERT INTO Monitor_Status (MonitorId,Status) VALUES (%u, 'NotRunning') ON DUPLICATE KEY UPDATE Status='NotRunning'", monitor->Id()); zmDbDo(sql); } @@ -428,7 +369,8 @@ int main(int argc, char *argv[]) { Image::Deinitialise(); Debug(1, "terminating"); logTerm(); + dbQueue.stop(); zmDbClose(); - return zm_terminate ? 0 : result; + return zm_terminate ? 0 : result; } diff --git a/src/zms.cpp b/src/zms.cpp index 9ad8f32c8..b38f405a1 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -23,9 +23,11 @@ #include "zm_signal.h" #include "zm_monitorstream.h" #include "zm_eventstream.h" -#include "zm_fifo.h" +#include "zm_fifo_stream.h" #include + #include +#include bool ValidateAccess(User *user, int mon_id) { bool allowed = true; @@ -86,6 +88,7 @@ int main(int argc, const char *argv[], char **envp) { zmLoadStaticConfig(); zmDbConnect(); zmLoadDBConfig(); + logInit(log_id_string); for (char **env = envp; *env != 0; env++) { char *thisEnv = *env; @@ -229,7 +232,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,10 +243,11 @@ int main(int argc, const char *argv[], char **envp) { } fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); - time_t now = time(nullptr); + time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 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); @@ -262,7 +267,7 @@ int main(int argc, const char *argv[], char **envp) { stream.setStreamQueue(connkey); stream.setStreamBuffer(playback_buffer); if ( !stream.setStreamStart(monitor_id) ) { - Error("Unable set start stream for monitor %d", monitor_id); + fputs("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n\r\n", stdout); stream.sendTextFrame("Unable to connect to monitor"); logTerm(); zmDbClose(); @@ -278,19 +283,9 @@ int main(int argc, const char *argv[], char **envp) { } else if ( mode == ZMS_SINGLE ) { stream.setStreamType(MonitorStream::STREAM_SINGLE); } else { -#if HAVE_LIBAVCODEC stream.setStreamFormat(format); stream.setStreamBitrate(bitrate); stream.setStreamType(MonitorStream::STREAM_MPEG); -#else // HAVE_LIBAVCODEC - Error("MPEG streaming of '%s' attempted while disabled", query); - fprintf(stderr, "MPEG streaming is disabled.\n" - "You should configure with the --with-ffmpeg" - " option and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return -1; -#endif // HAVE_LIBAVCODEC } stream.runStream(); } else if ( source == ZMS_FIFO ) { @@ -317,19 +312,9 @@ int main(int argc, const char *argv[], char **envp) { if ( mode == ZMS_JPEG ) { stream.setStreamType(EventStream::STREAM_JPEG); } else { -#if HAVE_LIBAVCODEC stream.setStreamFormat(format); stream.setStreamBitrate(bitrate); stream.setStreamType(EventStream::STREAM_MPEG); -#else // HAVE_LIBAVCODEC - Error("MPEG streaming of '%s' attempted while disabled", query); - fprintf(stderr, "MPEG streaming is disabled.\n" - "You should ensure the ffmpeg libraries are installed and detected" - " and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return -1; -#endif // HAVE_LIBAVCODEC } // end if jpeg or mpeg stream.runStream(); } else { @@ -337,7 +322,9 @@ int main(int argc, const char *argv[], char **envp) { } // 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 472f05043..ec4ae7b86 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -93,6 +93,7 @@ Options for use with monitors: #include "zm_monitor.h" #include "zm_local_camera.h" #include +#include void Usage(int status=-1) { fputs( @@ -182,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; } @@ -196,6 +197,7 @@ bool ValidateAccess(User *user, int mon_id, int function) { void exit_zmu(int exit_code) { logTerm(); + dbQueue.stop(); zmDbClose(); exit(exit_code); @@ -247,7 +249,7 @@ int main(int argc, char *argv[]) { {nullptr, 0, nullptr, 0} }; - const char *device = nullptr; + std::string device; int mon_id = 0; bool verbose = false; int function = ZMU_BOGUS; @@ -255,32 +257,35 @@ int main(int argc, char *argv[]) { int image_idx = -1; int scale = -1; int brightness = -1; + bool have_brightness = false; + int contrast = -1; + bool have_contrast = false; + int hue = -1; + bool have_hue = false; int colour = -1; + bool have_colour = false; + char *zoneString = nullptr; char *username = nullptr; char *password = nullptr; char *auth = nullptr; std::string jwt_token_str = ""; -#if ZM_HAS_V4L #if ZM_HAS_V4L2 int v4lVersion = 2; -#elif ZM_HAS_V4L1 - int v4lVersion = 1; -#endif // ZM_HAS_V4L2/1 -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 while (1) { int option_index = 0; 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 ) { + if (c == -1) { break; } switch (c) { case 'd': - if ( optarg ) + if (optarg) device = optarg; break; case 'm': @@ -294,7 +299,7 @@ int main(int argc, char *argv[]) { break; case 'i': function |= ZMU_IMAGE; - if ( optarg ) + if (optarg) image_idx = atoi(optarg); break; case 'S': @@ -302,7 +307,7 @@ int main(int argc, char *argv[]) { break; case 't': function |= ZMU_TIME; - if ( optarg ) + if (optarg) image_idx = atoi(optarg); break; case 'R': @@ -319,7 +324,7 @@ int main(int argc, char *argv[]) { break; case 'z': function |= ZMU_ZONES; - if ( optarg ) + if (optarg) zoneString = optarg; break; case 'a': @@ -351,23 +356,31 @@ int main(int argc, char *argv[]) { break; case 'B': function |= ZMU_BRIGHTNESS; - if ( optarg ) + if (optarg) { + have_brightness = true; brightness = atoi(optarg); + } break; case 'C': function |= ZMU_CONTRAST; - if ( optarg ) + if (optarg) { + have_contrast = true; contrast = atoi(optarg); + } break; case 'H': function |= ZMU_HUE; - if ( optarg ) + if (optarg) { + have_hue = true; hue = atoi(optarg); + } break; case 'O': function |= ZMU_COLOUR; - if ( optarg ) + if (optarg) { + have_colour = true; colour = atoi(optarg); + } break; case 'U': username = optarg; @@ -381,11 +394,11 @@ int main(int argc, char *argv[]) { case 'T': jwt_token_str = std::string(optarg); break; -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 case 'V': v4lVersion = (atoi(optarg)==1)?1:2; break; -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 case 'h': case '?': Usage(0); @@ -407,7 +420,7 @@ int main(int argc, char *argv[]) { Usage(); } - if ( device && !(function&ZMU_QUERY) ) { + if ( !device.empty() && !(function&ZMU_QUERY) ) { fprintf(stderr, "Error, -d option cannot be used with this option\n"); Usage(); } @@ -496,24 +509,32 @@ 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; } } if ( function & ZMU_TIME ) { - struct timeval timestamp = monitor->GetTimestamp(image_idx); - if ( verbose ) { + SystemTimePoint 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 ( image_idx == -1 ) - printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000); - else - printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000); + if (timestamp.time_since_epoch() != Seconds(0)) { + tm tm_info = {}; + time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp); + strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime_r(×tamp_t, &tm_info)); + } + Seconds ts_sec = std::chrono::duration_cast(timestamp.time_since_epoch()); + Microseconds ts_usec = std::chrono::duration_cast(timestamp.time_since_epoch() - ts_sec); + if (image_idx == -1) { + printf("Time of last image capture: %s.%02d\n", timestamp_str, static_cast(ts_usec.count())); + } + else { + printf("Time of image %d capture: %s.%02d\n", image_idx, timestamp_str, static_cast(ts_usec.count())); + } } else { - if ( have_output ) fputc(separator, stdout); - printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); + if (have_output) { + fputc(separator, stdout); + } + printf("%.2f", FPSeconds(timestamp.time_since_epoch()).count()); have_output = true; } } @@ -583,14 +604,20 @@ 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"); - int wait = 10*1000*1000; // 10 seconds - while ( ((state = monitor->GetState()) != Monitor::ALARM) and !zm_terminate and wait) { + + Microseconds wait_time = Seconds(10); + while ((monitor->GetState() != Monitor::ALARM) and !zm_terminate and wait_time > Seconds(0)) { // Wait for monitor to notice. - usleep(1000); - wait -= 1000; + Microseconds sleep = Microseconds(1); + std::this_thread::sleep_for(sleep); + wait_time -= sleep; } - if ( (state = monitor->GetState()) != Monitor::ALARM and !wait ) { + + if (monitor->GetState() != Monitor::ALARM and wait_time == Seconds(0)) { Error("Monitor failed to respond to forced alarm."); } else { printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); @@ -638,60 +665,60 @@ int main(int argc, char *argv[]) { monitor->DumpSettings(monString, verbose); printf("%s\n", monString); } - if ( function & ZMU_BRIGHTNESS ) { - if ( verbose ) { - if ( brightness >= 0 ) + if (function & ZMU_BRIGHTNESS) { + if (verbose) { + if (have_brightness) printf("New brightness: %d\n", monitor->actionBrightness(brightness)); else printf("Current brightness: %d\n", monitor->actionBrightness()); } else { - if ( have_output ) fputc(separator, stdout); - if ( brightness >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_brightness) printf("%d", monitor->actionBrightness(brightness)); else printf("%d", monitor->actionBrightness()); have_output = true; } } - if ( function & ZMU_CONTRAST ) { - if ( verbose ) { - if ( contrast >= 0 ) - printf("New brightness: %d\n", monitor->actionContrast(contrast)); + if (function & ZMU_CONTRAST) { + if (verbose) { + if (have_contrast) + printf("New contrast: %d\n", monitor->actionContrast(contrast)); else printf("Current contrast: %d\n", monitor->actionContrast()); } else { - if ( have_output ) fputc(separator, stdout); - if ( contrast >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_contrast) printf("%d", monitor->actionContrast(contrast)); else printf("%d", monitor->actionContrast()); have_output = true; } } - if ( function & ZMU_HUE ) { - if ( verbose ) { - if ( hue >= 0 ) + if (function & ZMU_HUE) { + if (verbose) { + if (have_hue) printf("New hue: %d\n", monitor->actionHue(hue)); else printf("Current hue: %d\n", monitor->actionHue()); } else { - if ( have_output ) fputc(separator, stdout); - if ( hue >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_hue) printf("%d", monitor->actionHue(hue)); else printf("%d", monitor->actionHue()); have_output = true; } } - if ( function & ZMU_COLOUR ) { - if ( verbose ) { - if ( colour >= 0 ) + if (function & ZMU_COLOUR) { + if (verbose) { + if (have_colour) printf("New colour: %d\n", monitor->actionColour(colour)); else printf("Current colour: %d\n", monitor->actionColour()); } else { - if ( have_output ) fputc(separator, stdout); - if ( colour >= 0 ) + if (have_output) fputc(separator, stdout); + if (have_colour) printf("%d", monitor->actionColour(colour)); else printf("%d", monitor->actionColour()); @@ -699,7 +726,7 @@ int main(int argc, char *argv[]) { } } - if ( have_output ) { + if (have_output) { printf("\n"); } if ( !function ) { @@ -707,35 +734,29 @@ int main(int argc, char *argv[]) { } } else { // non monitor functions if ( function & ZMU_QUERY ) { -#if ZM_HAS_V4L +#if ZM_HAS_V4L2 char vidString[0x10000] = ""; bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose); printf("%s", vidString); exit_zmu(ok ? 0 : -1); -#else // ZM_HAS_V4L +#else // ZM_HAS_V4L2 Error("Video4linux is required for device querying"); exit_zmu(-1); -#endif // ZM_HAS_V4L +#endif // ZM_HAS_V4L2 } 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); + 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++ ) { @@ -745,13 +766,14 @@ int main(int argc, char *argv[]) { 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", + SystemTimePoint timestamp = monitor->GetTimestamp(); + + printf( "%4d%5d%6d%9d%14.2f%6d%6d%8" PRIu64 "%8.2f\n", monitor->Id(), monitor_function, monitor->GetState(), monitor->GetTriggerState(), - tv.tv_sec, tv.tv_usec/10000, + FPSeconds(timestamp.time_since_epoch()).count(), monitor->GetLastReadIndex(), monitor->GetLastWriteIndex(), monitor->GetLastEventId(), @@ -759,13 +781,12 @@ int main(int argc, char *argv[]) { ); } } else { - struct timeval tv = { 0, 0 }; printf("%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, function, 0, 0, - tv.tv_sec, tv.tv_usec/10000, + 0l, 0l, 0, 0, 0, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 39c279aac..880a3d3db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,7 +12,13 @@ include(Catch) set(TEST_SOURCES - zm_crypt.cpp) + zm_box.cpp + zm_comms.cpp + zm_crypt.cpp + zm_font.cpp + zm_poly.cpp + zm_utils.cpp + zm_vector2.cpp) add_executable(tests main.cpp ${TEST_SOURCES}) @@ -29,3 +35,9 @@ target_include_directories(tests ${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/zm_box.cpp b/tests/zm_box.cpp new file mode 100644 index 000000000..e85056c85 --- /dev/null +++ b/tests/zm_box.cpp @@ -0,0 +1,52 @@ +/* + * 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_catch2.h" + +#include "zm_box.h" + +TEST_CASE("Box: default constructor") { + Box b; + + REQUIRE(b.Lo() == Vector2(0, 0)); + REQUIRE(b.Hi() == Vector2(0, 0)); + REQUIRE(b.Size() == Vector2(0, 0)); + REQUIRE(b.Area() == 0); +} + +TEST_CASE("Box: construct from lo and hi") { + Box b({1, 1}, {5, 5}); + + SECTION("basic properties") { + REQUIRE(b.Lo() == Vector2(1, 1)); + REQUIRE(b.Hi() == Vector2(5, 5)); + + REQUIRE(b.Size() == Vector2(4 ,4)); + REQUIRE(b.Area() == 16); + REQUIRE(b.Centre() == Vector2(3, 3)); + + REQUIRE(b.Vertices() == std::vector{{1, 1}, {5, 1}, {5, 5}, {1, 5}}); + } + + SECTION("contains") { + REQUIRE(b.Contains({0, 0}) == false); + REQUIRE(b.Contains({1, 1}) == true); + REQUIRE(b.Contains({3, 3}) == true); + REQUIRE(b.Contains({5, 5}) == true); + REQUIRE(b.Contains({6, 6}) == false); + } +} diff --git a/tests/zm_catch2.h b/tests/zm_catch2.h new file mode 100644 index 000000000..5f92277ea --- /dev/null +++ b/tests/zm_catch2.h @@ -0,0 +1,30 @@ +/* + * 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 ZONEMINDER_TESTS_ZM_CATCH2_H_ +#define ZONEMINDER_TESTS_ZM_CATCH2_H_ + +#include "catch2/catch.hpp" + +#include "zm_vector2.h" + +inline std::ostream &operator<<(std::ostream &os, Vector2 const &value) { + os << "{ X: " << value.x_ << ", Y: " << value.y_ << " }"; + return os; +} + +#endif //ZONEMINDER_TESTS_ZM_CATCH2_H_ diff --git a/tests/zm_comms.cpp b/tests/zm_comms.cpp new file mode 100644 index 000000000..5a0c41207 --- /dev/null +++ b/tests/zm_comms.cpp @@ -0,0 +1,335 @@ +/* + * 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_catch2.h" + +#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; + + SECTION("send/recv byte buffer") { + std::array msg = {'a', 'b', 'c'}; + std::array rcv{}; + + SECTION("on unbound socket") { + REQUIRE(client_socket.send(msg.data(), msg.size()) == -1); + REQUIRE(srv_socket.recv(rcv.data(), rcv.size()) == -1); + } + + SECTION("on bound socket") { + 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); + } + } + + SECTION("send/recv string") { + std::string msg = "abc"; + std::string rcv; + rcv.reserve(msg.length()); + + 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) == static_cast(msg.size())); + REQUIRE(srv_socket.recv(rcv) == static_cast(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 index d3a315ef4..94004527c 100644 --- a/tests/zm_crypt.cpp +++ b/tests/zm_crypt.cpp @@ -15,7 +15,7 @@ * with this program. If not, see . */ -#include "catch2/catch.hpp" +#include "zm_catch2.h" #include "zm_crypt.h" @@ -76,3 +76,62 @@ TEST_CASE("JWT validation") { REQUIRE(result.second == 0); } } + +TEST_CASE("zm::crypto::MD5") { + using namespace zm::crypto; + MD5 md5; + + REQUIRE(md5.GetDigest() == MD5::Digest()); + + SECTION("hash from const char*") { + md5.UpdateData("abcdefghijklmnopqrstuvwxyz"); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } + + SECTION("hash from std::string") { + md5.UpdateData(std::string("abcdefghijklmnopqrstuvwxyz")); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } +} + +TEST_CASE("zm::crypto::MD5::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SECTION("data and len") { + MD5::Digest digest = MD5::GetDigestOf(reinterpret_cast(data.data()), data.size()); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("container") { + MD5::Digest digest = MD5::GetDigestOf(data); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("multiple containers") { + MD5::Digest digest = MD5::GetDigestOf(data, data); + + REQUIRE(digest == MD5::Digest{0x44, 0x0a, 0xc8, 0x58, 0x92, 0xca, 0x43, 0xad, 0x26, 0xd4, 0x4c, 0x7a, 0xd9, 0xd4, + 0x7d, 0x3e}); + } +} + +TEST_CASE("zm::crypto::SHA1::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SHA1::Digest digest = SHA1::GetDigestOf(data); + + REQUIRE(digest == SHA1::Digest{0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, + 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}); +} diff --git a/tests/zm_font.cpp b/tests/zm_font.cpp new file mode 100644 index 000000000..432f26d7e --- /dev/null +++ b/tests/zm_font.cpp @@ -0,0 +1,164 @@ +/* + * 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_catch2.h" + +#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() + // TODO: restore capture initializer when C++14 is supported + int32 n = 0; + bool zero = true; + std::generate(bitmap.begin(), bitmap.end(), + [n, zero]() 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_poly.cpp b/tests/zm_poly.cpp new file mode 100644 index 000000000..c7e37673b --- /dev/null +++ b/tests/zm_poly.cpp @@ -0,0 +1,71 @@ +/* + * 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_catch2.h" + +#include "zm_poly.h" + +TEST_CASE("Polygon: default constructor") { + Polygon p; + + REQUIRE(p.Area() == 0); + REQUIRE(p.Centre() == Vector2(0, 0)); +} + +TEST_CASE("Polygon: construct from vertices") { + std::vector vertices{{{0, 0}, {6, 0}, {0, 6}}}; + Polygon p(vertices); + + REQUIRE(p.Area() == 18); + REQUIRE(p.Extent().Size() == Vector2(6, 6)); +} + +TEST_CASE("Polygon: clipping") { + // This a concave polygon in a shape resembling a "W" + std::vector v = { + {3, 1}, + {5, 1}, + {6, 3}, + {7, 1}, + {9, 1}, + {10, 8}, + {8, 8}, + {7, 5}, + {5, 5}, + {4, 8}, + {2, 8} + }; + + Polygon p(v); + + REQUIRE(p.GetVertices().size() == 11); + REQUIRE(p.Extent().Size() == Vector2(8, 7)); + + SECTION("boundary box larger than polygon") { + p.Clip(Box({1, 0}, {11, 9})); + + REQUIRE(p.GetVertices().size() == 11); + REQUIRE(p.Extent().Size() == Vector2(8, 7)); + } + + SECTION("boundary box smaller than polygon") { + p.Clip(Box({2, 4}, {10, 7})); + + REQUIRE(p.GetVertices().size() == 8); + REQUIRE(p.Extent().Size() == Vector2(8, 3)); + } +} diff --git a/tests/zm_utils.cpp b/tests/zm_utils.cpp new file mode 100644 index 000000000..43b6a4a71 --- /dev/null +++ b/tests/zm_utils.cpp @@ -0,0 +1,252 @@ +/* + * 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_catch2.h" + +#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("ByteArrayToHexString") { + std::vector bytes; + + REQUIRE(ByteArrayToHexString(bytes) == ""); + + bytes = {0x00}; + REQUIRE(ByteArrayToHexString(bytes) == "00"); + + bytes = {0x00, 0x01, 0x02, 0xff}; + REQUIRE(ByteArrayToHexString(bytes) == "000102ff"); +} + +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/tests/zm_vector2.cpp b/tests/zm_vector2.cpp new file mode 100644 index 000000000..625420486 --- /dev/null +++ b/tests/zm_vector2.cpp @@ -0,0 +1,94 @@ +/* + * 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_catch2.h" + +#include "zm_vector2.h" + +TEST_CASE("Vector2: default constructor") { + Vector2 c; + REQUIRE(c.x_ == 0); + REQUIRE(c.y_ == 0); +} + +TEST_CASE("Vector2: x/y constructor") { + Vector2 c(1, 2); + + REQUIRE(c.x_ == 1); + REQUIRE(c.y_ == 2); +} + +TEST_CASE("Vector2: assignment/copy") { + Vector2 c; + Vector2 c2(1, 2); + + REQUIRE(c.x_ == 0); + REQUIRE(c.y_ == 0); + + SECTION("assignment operator") { + c = c2; + REQUIRE(c.x_ == 1); + REQUIRE(c.y_ == 2); + } + + SECTION("copy constructor") { + Vector2 c3(c2); // NOLINT(performance-unnecessary-copy-initialization) + REQUIRE(c3.x_ == 1); + REQUIRE(c3.y_ == 2); + } +} + +TEST_CASE("Vector2: comparison operators") { + Vector2 c1(1, 2); + Vector2 c2(1, 2); + Vector2 c3(1, 3); + + REQUIRE((c1 == c2) == true); + REQUIRE((c1 != c3) == true); +} + +TEST_CASE("Vector2: arithmetic operators") { + Vector2 c(1, 1); + + SECTION("addition") { + Vector2 c1 = c + Vector2(1, 1); + REQUIRE(c1 == Vector2(2, 2)); + + c += {1, 2}; + REQUIRE(c == Vector2(2, 3)); + } + + SECTION("subtraction") { + Vector2 c1 = c - Vector2(1, 1); + REQUIRE(c1 == Vector2(0, 0)); + + c -= {1, 2}; + REQUIRE(c == Vector2(0, -1)); + } + + SECTION("scalar multiplication") { + c = c * 2; + REQUIRE(c == Vector2(2, 2)); + } +} + +TEST_CASE("Vector2: determinate") { + Vector2 v(1, 1); + REQUIRE(v.Determinant({0, 0}) == 0); + REQUIRE(v.Determinant({1, 1}) == 0); + REQUIRE(v.Determinant({1, 2}) == 1); +} diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 26c26aa31..6c8c06c00 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -58,6 +58,14 @@ case $i in PACKAGE_VERSION="${i#*=}" shift ;; + -x=*|--debbuild-extra=*) + DEBBUILD_EXTRA="${i#*=}" + shift + ;; + --dput=*) + DPUT="${i#*=}" + shift + ;; --default) DEFAULT=YES shift # past argument with no value @@ -80,7 +88,7 @@ fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,focal,groovy,hirsute" + DISTROS="bionic,focal,hirsute,impish" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; @@ -112,6 +120,11 @@ else if [ "$BRANCH" == "" ]; then #REV=$(git rev-list --tags --max-count=1) BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; + if [ -z "$BRANCH" ]; then + # This should only happen in CI environments where tag info isn't available + BRANCH=`cat version` + echo "Building branch $BRANCH" + fi if [ "$BRANCH" == "" ]; then echo "Unable to determine latest stable branch!" exit 0; @@ -216,8 +229,12 @@ rm -rf .git rm .gitignore cd ../ + if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then - tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig + read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then + tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig + fi; fi; IFS=',' ;for DISTRO in `echo "$DISTROS"`; do @@ -229,13 +246,10 @@ IFS=',' ;for DISTRO in `echo "$DISTROS"`; do fi; # Generate Changlog - if [ "$DISTRO" == "focal" ] || [ "$DISTRO" == "buster" ]; then - cp -Rpd distros/ubuntu2004 debian - elif [ "$DISTRO" == "beowulf" ] - then + if [ "$DISTRO" == "beowulf" ]; then cp -Rpd distros/beowulf debian else - cp -Rpd distros/ubuntu1604 debian + cp -Rpd distros/ubuntu2004 debian fi; if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then @@ -285,30 +299,37 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY EOF fi; + # Leave the .orig so that we don't pollute it when building deps + cd .. if [ $TYPE == "binary" ]; then - # Auto-install all ZoneMinder's depedencies using the Debian control file - sudo apt-get install devscripts equivs - sudo mk-build-deps -ir ./debian/control - echo "Status: $?" - DEBUILD=debuild + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir $DIRECTORY.orig/debian/control + echo "Status: $?" + DEBUILD=debuild else - if [ $TYPE == "local" ]; then - # Auto-install all ZoneMinder's depedencies using the Debian control file - sudo apt-get install devscripts equivs - sudo mk-build-deps -ir ./debian/control - echo "Status: $?" - DEBUILD="debuild -i -us -uc -b" - else - # Source build, don't need build depends. - DEBUILD="debuild -S -sa" - fi; + if [ $TYPE == "local" ]; then + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir $DIRECTORY.orig/debian/control + echo "Status: $?" + DEBUILD="debuild -i -us -uc -b" + else + # Source build, don't need build depends. + DEBUILD="debuild -S -sa" + fi; fi; + + cd $DIRECTORY.orig + if [ "$DEBSIGN_KEYID" != "" ]; then DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" fi + # Add any extra options specified on the CLI + DEBUILD="$DEBUILD $DEBBUILD_EXTRA" eval $DEBUILD if [ $? -ne 0 ]; then - echo "Error status code is: $?" + echo "Error status code is: $?" echo "Build failed."; exit $?; fi; @@ -340,12 +361,14 @@ EOF dput="Y"; if [ "$INTERACTIVE" != "no" ]; then read -p "Ready to dput $SC to $PPA ? Y/n..."; - if [[ "$REPLY" == [yY] ]]; then + if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then dput $PPA $SC fi; else - echo "dputting to $PPA"; - dput $PPA $SC + if [ "$DPUT" != "no" ]; then + echo "dputting to $PPA"; + dput $PPA $SC + fi; fi; fi; done; # foreach distro diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index d464089d3..2ea949078 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -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,10 +149,18 @@ 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 -# install the xenial deb and test zoneminder +# install the deb and test zoneminder install_deb () { # Check we've got gdebi installed @@ -153,7 +173,7 @@ install_deb () { exit 1 fi - # Install and test the zoneminder package (only) for Ubuntu Xenial + # Install and test the zoneminder package pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" if [ -e $pkgname ]; then @@ -200,8 +220,10 @@ 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}" - export RELEASE="${DIST}" + if [ "" == "$VERSION" ]; then + export VERSION="${versionfile}~${thedate}.${numcommits}" + fi + export RELEASE="${DIST}${PACKAGE_VERSION}" checkvars @@ -347,12 +369,10 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia setdebpkgname movecrud - if [ "${DIST}" == "focal" ] || [ "${DIST}" == "buster" ]; then + if [ "${DIST}" == "bionic" ] || [ "${DIST}" == "focal" ] || [ "${DIST}" == "hirsute" ] || [ "${DIST}" == "impish" ] || [ "${DIST}" == "buster" ] || [ "${DIST}" == "bullseye" ]; then ln -sfT distros/ubuntu2004 debian elif [ "${DIST}" == "beowulf" ]; then ln -sfT distros/beowulf debian - else - ln -sfT distros/ubuntu1604 debian fi setdebchangelog diff --git a/version b/version index 2e155ef29..9cf86ad0f 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.35.18 +1.37.1 diff --git a/web/ajax/event.php b/web/ajax/event.php index 1456bd192..a0b9cb57a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -5,7 +5,7 @@ if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) { ajaxError('No event id(s) supplied'); } -if ( canView('Events') ) { +if ( canView('Events') or canView('Snapshots') ) { switch ( $_REQUEST['action'] ) { case 'video' : if ( empty($_REQUEST['videoFormat']) ) { @@ -74,10 +74,15 @@ if ( canView('Events') ) { else $exportCompress = false; + if ( !empty($_REQUEST['exportStructure']) ) + $exportStructure = $_SESSION['export']['structure'] = $_REQUEST['exportStructure']; + else + $exportStructure = false; + session_write_close(); $exportIds = !empty($_REQUEST['eids']) ? $_REQUEST['eids'] : $_REQUEST['id']; - if ( $exportFile = exportEvents( + if ($exportFile = exportEvents( $exportIds, (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), $exportDetail, @@ -86,11 +91,14 @@ if ( canView('Events') ) { $exportVideo, $exportMisc, $exportFormat, - $exportCompress - ) ) - ajaxResponse(array('exportFile'=>$exportFile)); - else + $exportCompress, + $exportStructure, + (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport') + )) { + ajaxResponse(array('exportFile'=>$exportFile)); + } else { ajaxError('Export Failed'); + } break; case 'download' : require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); @@ -104,7 +112,7 @@ if ( canView('Events') ) { false,#detail false,#frames false,#images - $exportVideo, + true, #$exportVideo, false,#Misc $exportFormat, false#,#Compress diff --git a/web/ajax/events.php b/web/ajax/events.php index 8a8e3a4f1..090fe476e 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -6,28 +6,30 @@ $data = array(); // INITIALIZE AND CHECK SANITY // -if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username']; +if (!canView('Events')) + $message = 'Insufficient permissions for user '.$user['Username'].'
'; -if ( empty($_REQUEST['task']) ) { - $message = 'Must specify a task'; +if (empty($_REQUEST['task'])) { + $message = 'Must specify a task
'; } else { $task = $_REQUEST['task']; } -if ( empty($_REQUEST['eids']) ) { - if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied'; +if (empty($_REQUEST['eids'])) { + if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query') + $message = 'No event id(s) supplied
'; } else { $eids = $_REQUEST['eids']; } -if ( $message ) { +if ($message) { ajaxError($message); return; } require_once('includes/Filter.php'); $filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter(); -if ( $user['MonitorIds'] ) { +if ($user['MonitorIds']) { $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); } @@ -38,30 +40,46 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; // Bootstrap table sends json_ecoded array, which we must decode $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); +// Order specifies the sort direction, either asc or desc +$order = $filter->sort_asc() ? 'ASC' : 'DESC'; +if (isset($_REQUEST['order'])) { + if (strtolower($_REQUEST['order']) == 'asc') { + $order = 'ASC'; + } else if (strtolower($_REQUEST['order']) == 'desc') { + $order = 'DESC'; + } else { + Warning("Invalid value for order " . $_REQUEST['order']); + } +} + // Sort specifies the name of the column to sort on -$sort = 'StartDateTime'; -if ( isset($_REQUEST['sort']) ) { +$sort = $filter->sort_field(); +if (isset($_REQUEST['sort'])) { $sort = $_REQUEST['sort']; + if ($sort == 'EndDateTime') { + if ($order == 'ASC') { + $sort = 'EndDateTime IS NULL, EndDateTime'; + } else { + $sort = 'EndDateTime IS NOT NULL, EndDateTime'; + } + } } // 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']) ) ) { +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']) ) ) { +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']; @@ -72,25 +90,24 @@ if ( isset($_REQUEST['limit']) ) { // MAIN LOOP // -switch ( $task ) { +switch ($task) { case 'archive' : - foreach ( $eids as $eid ) archiveRequest($task, $eid); + 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') ) { + if (!canEdit('Events')) { ajaxError('Insufficient permissions for user '.$user['Username']); return; } - foreach ( $eids as $eid ) archiveRequest($task, $eid); + foreach ($eids as $eid) archiveRequest($task, $eid); break; case 'delete' : - if ( !canEdit('Events') ) { + if (!canEdit('Events')) { ajaxError('Insufficient permissions for user '.$user['Username']); return; } - - foreach ( $eids as $eid ) $data[] = deleteRequest($eid); + foreach ($eids as $eid) $data[] = deleteRequest($eid); break; case 'query' : $data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit); @@ -120,6 +137,8 @@ function deleteRequest($eid) { $message[] = array($eid=>'Event not found.'); } else if ( $event->Archived() ) { $message[] = array($eid=>'Event is archived, cannot delete it.'); + } else if (!$event->canEdit()) { + $message[] = array($eid=>'You do not have permission to delete event '.$event->Id()); } else { $event->delete(); } @@ -128,7 +147,6 @@ function deleteRequest($eid) { } function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { - $data = array( 'total' => 0, 'totalNotFiltered' => 0, @@ -137,7 +155,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim ); $failed = !$filter->test_pre_sql_conditions(); - if ( $failed ) { + if ($failed) { ZM\Debug('Pre conditions failed, not doing sql'); return $data; } @@ -152,7 +170,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim // 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)) ) { + if (!in_array($sort, array_merge($columns, $col_alt))) { ZM\Error('Invalid sort field: ' . $sort); $sort = 'Id'; } @@ -167,7 +185,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $storage_areas = ZM\Storage::find(); $StorageById = array(); - foreach ( $storage_areas as $S ) { + foreach ($storage_areas as $S) { $StorageById[$S->Id()] = $S; } @@ -176,41 +194,43 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim 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; - } - $event_ids[] = $event->Id(); - $unfiltered_rows[] = $row; - } # end foreach row + if (!$query) { + ajaxError(dbError($sql)); + return; } + while ($row = dbFetchNext($query)) { + $event = new ZM\Event($row); + $event->remove_from_cache(); + if (!$filter->test_post_sql_conditions($event)) { + continue; + } + $event_ids[] = $event->Id(); + $unfiltered_rows[] = $row; + } # end foreach row ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); $filtered_rows = null; - if ( count($advsearch) or $search != '' ) { + 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) ) { + if (count($advsearch)) { $terms = array(); - foreach ( $advsearch as $col=>$text ) { + 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 != '' ) { + } else if ($search != '') { $search = '%' .$search. '%'; $terms = array(); - foreach ( $columns as $col ) { + foreach ($columns as $col) { $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); } $terms[0]['obr'] = 1; @@ -220,15 +240,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim } # 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.'); + ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql); } else { $filtered_rows = $unfiltered_rows; } # end if search_filter->terms() > 1 + if ($limit) + $filtered_rows = array_slice($filtered_rows, $offset, $limit); + $returned_rows = array(); - foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { + foreach ($filtered_rows as $row) { $event = new ZM\Event($row); $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); diff --git a/web/ajax/log.php b/web/ajax/log.php index 7dfb11d6a..c5181e49b 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -168,8 +168,9 @@ function queryRequest() { foreach ( $results as $row ) { $row['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($row['TimeKey'])); - $Server = new ZM\Server($row['ServerId']); - $row['Server'] = $Server->Name(); + $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'])); diff --git a/web/ajax/modals/controlpreset.php b/web/ajax/modals/controlpreset.php index 124859507..e3cc02db3 100644 --- a/web/ajax/modals/controlpreset.php +++ b/web/ajax/modals/controlpreset.php @@ -1,22 +1,20 @@